JS專題之深淺拷貝

種瓜南山下發表於2019-02-16

前言

在開發過程中,偶爾會遇到這種場景,拿到一個資料後,你打算對它進行處理,但是你又希望拷貝一份副本出來,方便資料對比和以後恢復資料。

那麼這就涉及到了 JS 中對資料的深淺拷貝問題,所謂深淺拷貝,淺拷貝的意思就是,你只是複製了物件資料的引用,並沒有把記憶體裡的值另外複製一份,那麼深拷貝就是把值完整地複製一份新的值。

在之前的文章《JS專題之資料型別和型別檢測》中我有講過,JS 中的資料型別分為兩種,基本資料型別和引用資料型別,基本資料型別是儲存在棧的資料結構中的,是按值訪問,所以不存在深淺拷貝問題。

而比如物件,陣列,函式,正則,時間物件這些都是引用資料型別,是儲存在堆中的。所以,引用資料型別的複製,是記憶體地址的傳遞,並沒有拷貝出一份新的資料。

那麼深拷貝,淺拷貝的區別是什麼呢?先給結論:

操作拷貝之後的資料不會影響到原資料的值拷貝,就是深拷貝,反正,有影響則為淺拷貝。

一、應用場景

日常開發中,JS 拷貝大多會在 資料儲存,資料比對,資料同步 時出現,所以,當你在這些場景的時候,要記得裡面隱藏有一個資料深淺拷貝的問題。

二、淺拷貝

我們來看一下淺拷貝:

function clone(origin) {
    var result = {};
        for (var prop in origin) {
            if (origin.hasOwnProperty(prop)) {
                result[prop] = origin[prop];
            }
        }
        return result;
}

var jay = {
    name: "jayChou",
    age: 40,
    family: {
        wife: "Quinlivan"
    }
}

var otherJay = clone(jay);

otherJay.age = 18;
otherJay.family.wife = "otherGirl";

console.log(jay); 
// 
// {
//   name: "jayChou",
//   age: 40,  // 沒被改變
//   family: {
//     wife: "otherGirl"  // 同時被改變,說明是同一個引用
//   }
// }

console.log(otherJay);

// 
// {
//   name: "jayChou",
//   age: 18,
//   family: {
//     wife: "otherGirl"  // 被改變了
//   }
// }
複製程式碼

我們發現,首先,淺拷貝不是直接賦值,淺拷貝新建了一個物件,然後將源物件的屬性都一一複製過來,複製的是值,而不是引用。

我們知道,物件都是按地址引用進行訪問的,淺拷貝的複製只複製了第一層的屬性,並沒有遞迴將所有的值複製過來,所以,操作拷貝資料,對原資料產生了影響,故而為淺拷貝。

進而,那些可以直接返回原陣列的方法就可以簡單實現陣列和物件淺拷貝。

// 1、 陣列淺拷貝 - slice
function shallowCopy1(origin) {
    return origin.slice();
}

// 2、 陣列淺拷貝 - concat
function shallowCopy2(origin){
    return origin.concat();
}

// 3、 陣列淺拷貝 - 遍歷
function shallowCopy3(origin){
  var result = [];
  for(var i = 0; i < origin.length; i++) {
    result.push(origin[i]);
  }
  return result;
}


// 4、 物件淺拷貝 - Object.assign
function shallowCopy4(origin) {
    return Object.assign({},origin)
}

// 5、 物件淺拷貝 - 擴充套件運算子
// 擴充套件運算子(...)用於取出引數物件的所有可遍歷屬性,拷貝到當前物件之中
function shallowCopy5(origin) {
    return {
        ...origin
    }
}

複製程式碼

Object.assign 的拷貝,假如源物件的屬性值是一個指向物件的引用,它也只拷貝那個引用值。MDN 有相應的例項和解釋。

二、深拷貝

深拷貝就完整複製資料的值(而非引用),目的在於避免拷貝後資料對原資料產生影響。

目前深拷貝的實現方法主要有遞迴複製,JSON 正反序列化:

// 1. 深拷貝 - JSON 正反序列化
// 缺點就是無法拷貝 undefined、function、symbol 這類特殊的屬性值。
function deepClone1(origin) {
    return JSON.parse(JSON.stringify(arr));
}

// 2. 深拷貝 - 遞迴;
function deepClone2(origin) {
  const result = origin.constructor === Array ? [] : {};
  for (let keys in origin) {
    // 不遍歷原型鏈上的屬性
    if (origin.hasOwnProperty(keys)) {
      if (origin[keys] && typeof origin[keys] === "object") {
        // 如果值是物件,就遞迴一下, 區分是一般物件還是陣列物件
        result[keys] = origin[keys].constructor === Array ? [] : {};
        // 如果是引用資料型別,會遞迴呼叫
        result[keys] = deepClone(origin[keys]);
      } else {
        // 如果不是,就直接賦值
        result[keys] = origin[keys];
      }
    }
  }
  return result;
}

複製程式碼

JS 的深拷貝的應用,需要根據你的使用場景進行使用,首先是有無必要深拷貝,其次是資料型別,是否直接使用 JSON 的 API 其實就可以。

JS 深淺拷貝在日常開發中使用頻率還是較高的,其中考察的知識點,主要在於:

1、是否遇到過深淺拷貝的問題,裡面有什麼坑
2、是否瞭解 JS 的資料型別,資料在計算機中的儲存機制
3、是否瞭解陣列、物件的一些常用的 API
4、jquery、lodash、underscore 的相關工具函式使用

總結

深淺拷貝主要考察了開發者對 JS 資料型別的瞭解,陣列,物件常用方法的特點和應用,遞迴函式的封裝。

春節快樂!
寫於大年三十,不寫文章渾身不舒服~ 歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。

JS專題之深淺拷貝

掘金專欄 JavaScript 系列文章

  1. JavaScript之變數及作用域
  2. JavaScript之宣告提升
  3. JavaScript之執行上下文
  4. JavaScript之變數物件
  5. JavaScript之原型與原型鏈
  6. JavaScript之作用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中徹底理解this
  12. JavaScript專題之模擬實現call和apply
  13. JavaScript專題之模擬實現bind
  14. JavaScript專題之模擬實現new
  15. JS專題之事件模型
  16. JS專題之事件迴圈
  17. JS專題之去抖函式
  18. JS專題之節流函式
  19. JS專題之函式柯里化
  20. JS專題之陣列去重

相關文章