陣列深拷貝_JS深淺拷貝的深入淺出
技術標籤:陣列深拷貝
一 首先了解JavaScript中的基本資料型別
基本資料型別:String,Number,Boolean,Null,Undefined
引用資料型別:Araay,Date,RegExp,Function
二 基本資料型別和引用資料型別的區別?
(1)它們儲存的位置不同:基本資料儲存在棧中,引用資料型別儲存在堆記憶體中。JS對引用資料的操作其實操作物件的引用而不是實際的物件,也就是指向實際物件的記憶體地址,如果obj1拷貝了obj2,那麼這兩個物件指向了同一堆記憶體物件,具體就是吧obj1棧記憶體中的引用地址複製了一份給obj2,所以他們指向了同一個堆記憶體物件。
那為什麼基本資料型別要存在棧記憶體中,而引用資料要存在堆記憶體中?
(1)堆比棧大,棧的查詢速度比堆塊。
(2)基本資料型別比較的穩定,相對的話佔用的記憶體比較小
(3)引用資料型別一般都是動態的,而且可能是無限大,引用的值也經常改變,所以不能放在棧中,這樣會降低查詢的速度,因此放在變數棧中的值應該是指向該物件再堆記憶體中的地址,地址的大小的固定的,所以吧他存在棧中對變數的效能沒有影響。
JS一般在訪問存在堆記憶體的物件的時候是不能直接訪問的,所以在訪問物件的時候,要先獲取改物件再堆記憶體中的地址,在根據改地址去訪問該物件中的值。
(2)基本資料型別可以使用typeof可以返回基本資料型別,但是Null會返回object,所以Null表示一個空物件指標;引用資料型別使用typeof會返回object,所以引用資料型別要用instanceof來檢測引用資料的型別。
(3)定義引用資料型別需要使用new操作符,後面在跟一個建構函式來建立,或者使用物件字面量表示法建立物件。
使用new操作符建立物件
var obj1 = new Object();obj1.a = 1;
使用物件字面量表示法建立物件
var obj1 = { a: 1, b: 2}
基本資料型別 name和value值都是儲存在棧中
當b=a的時候
棧記憶體開闢了一個新記憶體出來,所以在修改a的值的時候不會影響到b的值
引用資料型別-name是存在棧中,value存在堆記憶體中,但是棧記憶體會提供一個引用地址指向該物件在堆記憶體中的值
當b=a拷貝時,其實複製的是a的引用地址,並不是堆記憶體中的值
當你修改a裡面的值的時候,由於a與b指向的是同一個地址所以b也就受到了影響,這就是淺拷貝。
如果在堆記憶體中開闢了一個新的記憶體地址專門存在b的值的話,那就達到了深拷貝的效果了。
三 什麼是深拷貝和淺拷貝
首先深拷貝和淺拷貝都只針對於引用型別的資料;淺拷貝只複製指向某個物件的指標,並不複製物件的本身,新原物件還是共享同一塊記憶體,但是深拷貝會創造一個一模一樣的新物件出來,新物件跟原物件不再共享同一塊記憶體地址,修改新物件也不會影響到原物件。
區別:淺拷貝只複製物件的第一層屬性,深拷貝可以對物件的屬性進行遞迴複製。
四 實現深拷貝
1、json物件的parse和stringfy
function deepClone (obj) { let _obj = JSON.stringify(obj) let objClone = JSON.parse(_obj) return objClone} let a = [0,1,2,3,4]let b = deepClone(a) a[0] = 1 console.log(a) //[1,1,2,3,4]console.log(b) //[0,1,2,3,4]
2、遞迴複製所有層級屬性
function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { if (obj1.hasOwnProperty(i)) { // 如果子屬性為引用資料型別,遞迴複製 if (obj1[i] && typeof obj1[i] === "object") { obj2[i] = deepCopy(obj1[i]); } else { // 如果是基本資料型別,只是簡單的複製 obj2[i] = obj1[i]; } } } } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = deepCopy(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // 1 alert(obj2.a); // 3 alert(obj1.c.d); // 3 alert(obj2.c.d); // 4
缺陷:當遇到兩個互相引用的物件,會出現死迴圈的情況,為了避免相互引用的物件導致死迴圈的情況,則應該在遍歷的時候判斷是否相互引用物件,如果是則退出迴圈;
function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { var prop = obj1[i]; // 避免相互引用造成死迴圈,如obj1.a=obj if (prop == obj1) { continue; } if (obj1.hasOwnProperty(i)) { // 如果子屬性為引用資料型別,遞迴複製 if (prop && typeof prop === "object") { obj2[i] = (prop.constructor === Array) ? [] : {}; arguments.callee(prop, obj2[i]); // 遞迴呼叫 } else { // 如果是基本資料型別,只是簡單的複製 obj2[i] = prop; } } } } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = deepCopy(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // 1 alert(obj2.a); // 3 alert(obj1.c.d); // 3 alert(obj2.c.d); // 4
// Object.create實現深拷貝1,但也只能拷貝一層function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { var prop = obj1[i]; // 避免相互引用造成死迴圈,如obj1.a=obj if (prop == obj1) { continue; } if (obj1.hasOwnProperty(i)) { // 如果子屬性為引用資料型別,遞迴複製 if (prop && typeof prop === "object") { obj2[i] = (prop.constructor === Array) ? [] : Object.create(prop); } else { // 如果是基本資料型別,只是簡單的複製 obj2[i] = prop; } } } } return obj2; } var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = deepCopy(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // 1 alert(obj2.a); // 3 alert(obj1.c.d); // 3 alert(obj2.c.d); // 4
// Object實現拷貝2,淺拷貝var obj1 = { a: 1, b: 2, c: { d: 3 } } var obj2 = Object.create(obj1); obj2.a = 3; obj2.c.d = 4; alert(obj1.a); // 1 alert(obj2.a); // 3 alert(obj1.c.d); // 4 alert(obj2.c.d); // 4
3、jquery的extends方法
$.extend([deep ], target, object1 [, objectN ])
deep表示是否深拷貝,為true為深拷貝;為false,為淺拷貝。
targetObject型別 目標物件,其他物件的成員屬性將被附加到該物件上。
object1objectN可選。Object型別 第一個以及第N個被合併的物件。
let a = [0,1,[2,3],4]let b = $.extend(true, [], a)a[0] = 1a[2][0] = 1 // [1,1,[1,3],4]b // [0,1,[2,3],4]
4、lodash函式庫實現深拷貝
let result = _.cloneDeep(test)
5、Reflect法
// 代理法function deepClone(obj) { if (!isObject(obj)) { throw new Error('obj 不是一個物件!') } let isArray = Array.isArray(obj) let cloneObj = isArray ? [...obj] : { ...obj } Reflect.ownKeys(cloneObj).forEach(key => { cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return cloneObj}
6、用slice實現對陣列的深拷貝
// 當數組裡面的值是基本資料型別,比如String,Number,Boolean時,屬於深拷貝// 當數組裡面的值是引用資料型別,比如Object,Array時,屬於淺拷貝var arr1 = ["1","2","3"]; var arr2 = arr1.slice(0);arr2[1] = "9";console.log("陣列的原始值:" + arr1 );console.log("陣列的新值:" + arr2 );
7.用concat實現對陣列的深拷貝
// 當數組裡面的值是基本資料型別,比如String,Number,Boolean時,屬於深拷貝var arr1 = ["1","2","3"];var arr2 = arr1.concat();arr2[1] = "9";console.log("陣列的原始值:" + arr1 );console.log("陣列的新值:" + arr2 );// 當數組裡面的值是引用資料型別,比如Object,Array時,屬於淺拷貝var arr1 = [{a:1},{b:2},{c:3}];var arr2 = arr1.concat();arr2[0].a = "9";console.log("陣列的原始值:" + arr1[0].a ); // 陣列的原始值:9console.log("陣列的新值:" + arr2[0].a ); // 陣列的新值:9
五 實現淺拷貝
for···in只迴圈第一層
// 只複製第一層的淺拷貝function simpleCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : {}; for (let i in obj1) { obj2[i] = obj1[i]; } return obj2;}var obj1 = { a: 1, b: 2, c: { d: 3 }}var obj2 = simpleCopy(obj1);obj2.a = 3;obj2.c.d = 4;alert(obj1.a); // 1alert(obj2.a); // 3alert(obj1.c.d); // 4alert(obj2.c.d); // 4
Object.assign方法
var obj = { a: 1, b: 2}var obj1 = Object.assign(obj);obj1.a = 3;console.log(obj.a) // 3
直接用=賦值
let a=[0,1,2,3,4], b=a;console.log(a===b);a[0]=1;console.log(a,b);
微信公眾號“學識鋪子” 回覆 “拷貝”即可閱讀