在 JavaScript 中深度克隆對象(及其工做原理)

做者:Chris Chujavascript

翻譯:瘋狂的技術宅前端

原文:alligator.io/js/deep-clo…java

未經容許嚴禁轉載前端工程化

若是你打算用 JavaScript 進行編碼,那麼就須要瞭解對象的工做方式。對象是 JavaScript 最重要的元素之一,深刻理解了它會使你在編碼時駕輕就熟。在克隆對象時,它並不像看起來那麼簡單。函數

當你不想改變原始對象時,就須要克隆對象。例如,若是你有一個接受對象並改變它的函數,可能不想改變其原始對象。工具


那麼讓咱們在 JavaScript 中建立一個對象:ui

let testObject = {
  a: 1,
  b: 2,
  c: 3
};
複製代碼

在上面的代碼片斷中,咱們初始化一個新對象並將其分配給變量 testObject。如今對於大多數初學者來講,他們會試着經過將 testObject 分配給新變量來建立這個對象的副本,以便在其代碼中進行操做。很抱歉用這種方法行不通。編碼

下面是一個代碼片斷,說明了爲何不起做用。spa

let testObject = {
  a: 1,
  b: 2,
  c: 3
};

// 爲 testObject 建立一個副本
let testObjectCopy = testObject;

testObject.a = 9;
console.log(testObjectCopy.a);
// 這裏 a = 9
複製代碼

如上面的代碼片斷所示,建立新變量 testObjectCopy 實際上並不建立 testObject 的副本。相反它只是引用 testObject。你對所謂的副本作的任何更改也將反映在原始對象中。prototype

循環遍歷對象並將每一個屬性複製到新對象也不起做用。

const copyObject = object => {
  // 這是存儲原始對象屬性的對象
  let copiedObj = {};
  
  for (let key in object) {
    // 這裏將每一個屬性從原始對象複製到複製對象
    copiedObj[key] = object[key];
  }

  return copiedObj;
};

const testObject = {
  a: 5,
  b: 6,
  c: {
    d: 4
  }
};

copyObject(testObject);
複製代碼

上述方法存在如下幾個問題:

  • 1. 將每一個屬性複製到新對象的循環只會複製對象上的可枚舉屬性。可枚舉屬性是將要出如今 for 循環和 Object.keys 中的屬性。
  • 2. 複製的對象有一個新的 Object.prototype 方法,這不是複製對象時所需的方法。
  • 3. 若是對象具備做爲對象的屬性,則複製的對象實際上將會引用原始對象而不是建立副本。這意味着若是更改複製對象中的嵌套對象,原始對象也會更改。
  • 4. 不復制任何屬性描述符。若是將 configurablewritable 設置爲 false,則複製對象中的屬性描述符將會默認爲 true

那麼應該怎樣正確的複製對象?

對於僅存儲基本類型(如數字和字符串)的簡單對象,上述淺層複製方法將起做用。可是若是對象具備對其餘嵌套對象的引用,則不會複製實際對象。你只會複製對其的引用

對於深層複製,最簡單的選擇是使用可靠的外部庫,如Lodash

使用 Lodash 的 Clone 和 Clonedeep

Lodash 提供兩種不一樣的功能,容許你進行淺拷貝和深拷貝,它們是 cloneclonedeep。 Lodash 的優勢在於你能夠單獨導入它的每一個函數,而無需將整個庫放入你的項目中。這能夠大大的減小依賴項的大小。

const clone = require('lodash/clone'); 
const cloneDeep = require('lodash/clonedeep');

// 你也能夠這樣作:
// const clone = require('lodash.clone');
// const cloneDeep = require('lodash.clonedeep');
// 取決於你本身的風格 :)
複製代碼

如今就用 cloneclonedeep 函數作一些嘗試:

const clone = require('lodash/clone'); 
const cloneDeep = require('lodash/clonedeep');

const externalObject = {
  animal: 'Gator'
};

const originalObject = {
  a: 1,
  b: 'string',
  c: false,
  d: externalObject
};

const shallowClonedObject = clone(originalObject);

externalObject.animal = 'Crocodile';

console.log(originalObject);
console.log(shallowClonedObject);
// originalObject 和 shallowClonedObject 中的`animal`屬性
// 是同時被改變的,由於它是一個淺的副本。

const deepClonedObject = clonedeep(originalObject);

externalObject.animal = 'Lizard';

console.log(originalObject);
console.log(deepClonedObject);
// 原始對象中的'animal'屬性發生了變化,但對於
// deepClonedObject,它複製後仍然是'Crocodile'
// 對象是獨立的而不是複製引用。
複製代碼

在上面的代碼中,咱們建立了一個名爲 originalObject 的對象,它存儲了 7 個屬性,每一個屬性都有不一樣的值。屬性 d 引用咱們的 externalObject,它具備值爲 Gatoranimal 的屬性。

當從 Lodash 執行 clone 函數時,它會建立一個對象的淺層副本,咱們將其分配給 shallowClonedObject。在 externalObject 中爲 animal 屬性賦值一個新值將改變 originalObjectshallowClonedObject,由於淺拷貝只能將引用複製到 externalObject並它沒有爲本身創造一個全新的對象。

這就是 clonedeep 函數的用武之地。若是你對 deepClonedObject 執行相同的處理,那麼 originalObjectd 屬性是惟一要改變的屬性。

🤓試一試,看看它如何幫助你編碼!🚀

歡迎關注前端公衆號:前端先鋒,獲取前端工程化實用工具包。

相關文章
相關標籤/搜索