1. 程式人生 > 其它 >陣列深拷貝_JS深淺拷貝的深入淺出

陣列深拷貝_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值都是儲存在棧中

c3147603d0e46f672f24e598cee0a333.png

當b=a的時候

b2bdf777d665d482887c3bb21b1f40b0.png

棧記憶體開闢了一個新記憶體出來,所以在修改a的值的時候不會影響到b的值

引用資料型別-name是存在棧中,value存在堆記憶體中,但是棧記憶體會提供一個引用地址指向該物件在堆記憶體中的值

ecccdd08620ef879a6ea453d529352dd.png

當b=a拷貝時,其實複製的是a的引用地址,並不是堆記憶體中的值

a41efb70409988d6707c9b22d5eee965.png

當你修改a裡面的值的時候,由於a與b指向的是同一個地址所以b也就受到了影響,這就是淺拷貝。

0f75510239cada0077c07a61a724d5cb.png

如果在堆記憶體中開闢了一個新的記憶體地址專門存在b的值的話,那就達到了深拷貝的效果了。

82404e2ef732e9a805ad3dbee0408a1e.png

三 什麼是深拷貝和淺拷貝

首先深拷貝和淺拷貝都只針對於引用型別的資料;淺拷貝只複製指向某個物件的指標,並不複製物件的本身,新原物件還是共享同一塊記憶體,但是深拷貝會創造一個一模一樣的新物件出來,新物件跟原物件不再共享同一塊記憶體地址,修改新物件也不會影響到原物件。

區別:淺拷貝只複製物件的第一層屬性,深拷貝可以對物件的屬性進行遞迴複製。

四 實現深拷貝

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);

微信公眾號“學識鋪子” 回覆 “拷貝”即可閱讀

628ce04fcff1fa8354d444fafbc6247e.png