JS賦值、淺拷貝和深拷貝(陣列和物件的深淺拷貝)例項詳解
本文例項講述了JS賦值、淺拷貝和深拷貝(陣列和物件的深淺拷貝)。分享給大家供大家參考,具體如下:
深拷貝和淺拷貝是隻針對Object和Array這樣的引用資料型別的。
淺拷貝
只是拷貝了基本型別的資料,而引用型別資料,複製後也是會發生引用,我們把這種拷貝叫做淺拷貝(淺複製)
淺拷貝只複製指向某個物件的指標,而不復制物件本身,新舊物件還是共享同一塊記憶體。但深拷貝會另外創造一個一模一樣的物件,新物件跟原物件不共享記憶體,修改新物件不會改到原物件。
賦值和淺拷貝的區別
當我們把一個物件賦值給一個新的變數時,賦的其實是該物件的在棧中的地址,而不是堆中的資料。也就是兩個物件指向的是同一個儲存空間,無論哪個物件發生改變,其實都是改變的儲存空間的內容,因此,兩個物件是聯動的。
淺拷貝是按位拷貝物件,它會建立一個新物件,這個物件有著原始物件屬性值的一份精確拷貝。如果屬性是基本型別,拷貝的就是基本型別的值;如果屬性是記憶體地址(引用型別),拷貝的就是記憶體地址 ,因此如果其中一個物件改變了這個地址,就會影響到另一個物件。即預設拷貝建構函式只是對物件進行淺拷貝複製(逐個成員依次拷貝),即只複製物件空間而不復制資源。
我們先來看兩個例子,對比賦值與淺拷貝會對原物件帶來哪些改變?
// 物件賦值 var obj1 = { 'name' : 'zhangsan','age' : '18','language' : [1,[2,3],[4,5]],}; var obj2 = obj1; obj2.name = "lisi"; obj2.language[1] = ["二","三"]; console.log('obj1',obj1) console.log('obj2',obj2)
// 淺拷貝 var obj1 = { 'name' : 'zhangsan',}; var obj3 = shallowCopy(obj1); obj3.name = "lisi"; obj3.language[1] = ["二","三"]; function shallowCopy(src) { var dst = {}; for (var prop in src) { if (src.hasOwnProperty(prop)) { dst[prop] = src[prop]; } } return dst; } console.log('obj1',obj1) console.log('obj3',obj3)
上面例子中,obj1是原始資料,obj2是賦值操作得到,而obj3淺拷貝得到。我們可以很清晰看到對原始資料的影響,具體請看下錶:
1、Object.assign()
Object.assign()
方法可以把任意多個的源物件自身的可列舉屬性拷貝給目標物件,然後返回目標物件。但是 Object.assign()
進行的是淺拷貝,拷貝的是物件的屬性的引用,而不是物件本身。
var obj = { a: {a: "kobe",b: 39} }; var initalObj = Object.assign({},obj); initalObj.a.a = "wade"; console.log(obj.a.a); //wade
注意:當object只有一層的時候,是深拷貝
let obj = { username: 'kobe' }; let obj2 = Object.assign({},obj); obj2.username = 'wade'; console.log(obj);//{username: "kobe"}
2、Array.prototype.concat()
let arr = [1,3,{ username: 'kobe' }]; let arr2=arr.concat(); arr2[2].username = 'wade'; console.log(arr)
修改新物件會改到原物件:
3、Array.prototype.slice()
let arr = [1,{ username: ' kobe' }]; let arr3 = arr.slice(); arr3[2].username = 'wade' console.log(arr);
同樣修改新物件會改到原物件:
關於Array的slice和concat方法的補充說明:Array的slice和concat方法不修改原陣列,只會返回一個淺複製了原陣列中的元素的一個新陣列。
原陣列的元素會按照下述規則拷貝:
-
如果該元素是個物件引用(不是實際的物件),slice 會拷貝這個物件引用到新的數組裡。兩個物件引用都引用了同一個物件。如果被引用的物件發生改變,則新的和原來的陣列中的這個元素也會發生改變。
-
對於字串、數字及布林值來說(不是 String、Number 或者 Boolean 物件),slice 會拷貝這些值到新的數組裡。在別的數組裡修改這些字串或數字或是布林值,將不會影響另一個數組。
可能這段話晦澀難懂,我們舉個例子,將上面的例子小作修改:
let arr = [1,{ username: ' kobe' }]; let arr3 = arr.slice(); arr3[1] = 2 console.log(arr,arr3);
4.ES6
let {...b} = a; let [...b]= a; //當object只有一層的時候,是深拷貝
深拷貝
深拷貝:在計算機中開闢了一塊新的記憶體地址用於存放複製的物件。(對屬性中所有引用型別的值,遍歷到是基本型別的值為止。)
實現方式
1、JSON.parse(JSON.stringify())
let arr = [1,{ username: ' kobe' }]; let arr4 = JSON.parse(JSON.stringify(arr)); arr4[2].username = 'duncan'; console.log(arr,arr4)
原理: 用JSON.stringify將物件轉成JSON字串,再用JSON.parse()把字串解析成物件,一去一來,新的物件產生了,而且物件會開闢新的棧,實現深拷貝。
這種方法雖然可以實現陣列或物件深拷貝,但不能處理函式。
let arr = [1,{ username: ' kobe' },function(){}]; let arr4 = JSON.parse(JSON.stringify(arr)); arr4[2].username = 'duncan'; console.log(arr,arr4)
這是因為 JSON.stringify()
方法是將一個JavaScript值(物件或者陣列)轉換為一個 JSON字串,不能接受函式。
2、手寫遞迴方法
遞迴方法實現深度克隆原理:遍歷物件、陣列直到裡邊都是基本資料型別,然後再去複製,就是深度拷貝。
//定義檢測資料型別的功能函式 function checkedType(target) { return Object.prototype.toString.call(target).slice(8,-1) } //實現深度克隆---物件/陣列 function clone(target) { //判斷拷貝的資料型別 //初始化變數result 成為最終克隆的資料 let result,targetType = checkedType(target) if (targetType === 'object') { result = {} } else if (targetType === 'Array') { result = [] } else { return target } //遍歷目標資料 for (let i in target) { //獲取遍歷資料結構的每一項值。 let value = target[i] //判斷目標結構裡的每一值是否存在物件/陣列 if (checkedType(value) === 'Object' || checkedType(value) === 'Array') { //物件/數組裡嵌套了物件/陣列 //繼續遍歷獲取到value值 result[i] = clone(value) } else { //獲取到value值是基本的資料型別或者是函式。 result[i] = value; } } return result }
感興趣的朋友可以使用線上HTML/CSS/JavaScript程式碼執行工具:http://tools.jb51.net/code/HtmlJsRun測試上述程式碼執行效果。
更多關於JavaScript相關內容感興趣的讀者可檢視本站專題:《JavaScript陣列操作技巧總結》、《JavaScript遍歷演算法與技巧總結》、《javascript面向物件入門教程》、《JavaScript數學運算用法總結》、《JavaScript資料結構與演算法技巧總結》及《JavaScript錯誤與除錯技巧總結》
希望本文所述對大家JavaScript程式設計有所幫助。