輕鬆掌握深拷貝和淺拷貝

首先,咱們必須明確一點,就是JavaScript的變量能夠分爲如下兩種類型:javascript

  • 基本類型
    • undefined
    • null
    • number
    • string
    • boolean
    • symbol

基本變量是直接按值存放的,存放在棧內存中的簡單數據段,能夠直接訪問。java

  • 引用類型 Object

存放在堆內存中的對象,變量保存的是一個指針,這個指針指向另外一個位置。當須要訪問引用類型(如對象,數組等)的值時,首先從棧內存中得到該對象的地址指針,而後再從堆內存中取得所需的數據。正則表達式

一個簡單的例子數組

var a = 2;
var obj1 = {b:2};
var obj2 = obj1;

obj2.b = 3; 

console.log(obj1.b); // 3
console.log(obj2.b); // 3
複製代碼

那麼問題就來了,有一些場景咱們須要將一個對象含的值所有Copy給另外一個對象,這個時候若是隻是簡單的賦值操做,只是對指針進行了一個複製,而在堆內存區的值並無發生改變。因此咱們獲得如下的結論:bash

什麼是深拷貝

深拷貝便是在堆內存區拷貝出一個對象來。函數

深拷貝是開闢一塊新的內存地址,將原對象的各個屬性逐個複製進去。對拷貝對象和源對象各自的操做互不影響。工具

實現淺拷貝

以前一直有一個錯誤實現深拷貝的想法,就是遍歷一個對象的k-v對並一一複製給另外一個對象即可以實現深拷貝ui

可是是錯誤的,這個是淺拷貝(shallowCopy)spa

緣由很簡單,當K-V對裏valueObject的時候,複製過去便仍然是複製引用。好比prototype

let obj = {
    a: 1,
    b:{
        c: 2,
        d: 3
    }
}

let obj2 = {}

for(let item of Object.keys(obj)){
    obj2[item] = obj[item]
}

obj2.b.d = 2; 

obj.b.d // 此時obj.b.d 變成了2
複製代碼

另一點

Object.assign也是一個shallowCopy

let obj1 = { a: 0 , b: { c: 0}};
  let obj2 = Object.assign({}, obj1);
  console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
  
  obj1.a = 1;
  console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
  console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
  
  obj2.a = 2;
  console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
  console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 0}}
  
  obj2.b.c = 3;
  console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}}
  console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}}
  
複製代碼

如何實現深拷貝

  • 方法一:JSON對象的parse和stringify(最簡單的)
let obj1 = { a: 0 , b: { c: 0}};

let obj2 = JSON.parse(JSON.stringify(obj1));

obj2.b.c = 3;
console.log(obj2.b.c); // { a: 0 , b: { c: 3}};

console.log(obj1.b.c); // { a: 0 , b: { c: 0}};

複製代碼

該方法夠處理JSON格式能表示的全部數據類型,可是沒法拷貝對象裏面的函數正則表達式等,並且會喪失全部的constructor,也就是說,將是破壞整條prototype鏈。

  • 方法二:遞歸淺拷貝

剛纔提到淺拷貝只能拷貝對象的一層,那麼對淺拷貝進行遞歸即可以實現深拷貝。

function deepCopy (oldObj, newObj){
    for(let key in oldObj){
        if(typeof oldObj[key] != 'object'){
            // 是基本類型直接複製
            newObj[key] = oldObj[key];
        }else {
            newObj[key] = oldObj[key].constructor == '[Function: Array]'?[]:{};
            deepCopy(oldObj[key],newObj[key])
        }
    }
}
複製代碼

arguments.calle能夠在匿名函數中實現遞歸,此處也能夠用deepCopy(oldObj[key],newObj[key]),該方法的缺陷是,一旦欲拷貝對象和原對象存在相互引用的狀況,即可能形成死循環。(一直往下遞歸仍然判斷爲Object即形成死循環)因此須要加上判斷跳出的語句

...
for(let key in oldObj){
    if(newObj[key] === oldObj[key]){
        continue;
    }
    ...
}
...
複製代碼
  • 方法三:借用工具庫loadash

loadash有一個.cloneDeep的方法能夠實現深拷貝。

使用方法:

obj2 = _.cloneDeep(obj1);

綜上:

  • 基本類型變量存貯在棧內存區,存放在堆內存中的對象,變量保存的是一個指針。
  • 直接遍歷對象一一複製是淺拷貝(shallowCopy)
  • 深拷貝便是在堆內存區拷貝出一個對象來。
  • 深拷貝固然更佔內存,請必定要針對不一樣的場景作不一樣的拷貝處理。
相關文章
相關標籤/搜索