JavaScript中深度克隆对象的工作原理

来自菜鸟教程
跳转至:导航、​搜索

介绍

如果您打算使用 JavaScript 进行编码,则需要了解对象的工作原理。 对象是 JavaScript 中最重要的元素之一,深入了解对象总是有用的。

了解如何在 JavaScript 中正确克隆对象很重要。 可以创建对象的浅拷贝和深拷贝。 对象的浅拷贝引用原始对象。 因此,对原始对象所做的任何更改都将反映在副本中。 深层副本是原始对象的所有元素的副本。 对原始对象所做的更改不会反映在副本中。 在本文中,您将使用 Lodash 库 创建对象的深层副本。

先决条件

要完成本教程,您将需要以下内容:

第 1 步 – 通过重新分配对象创建浅拷贝

如果您创建一个接收对象并更改它的函数,您可能想要创建对象的副本并更改副本而不是改变原始对象。

初始化一个新对象并将其分配给变量 testObject。 此对象应具有字母 abc 作为键和 123作为值,分别。

在 JavaScript 中创建对象:

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

现在,尝试通过将 testObject 分配给名为 testObjectCopy 的新变量来创建此对象的副本以进行操作:

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

let testObjectCopy = testObject;

更改 testObject 中键 a 的值。 将其设置为 9

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

let testObjectCopy = testObject;

testObject.a = 9;

您可能希望此更改仅反映在 testObject 对象中。 使用 console.log 语句检查 atestObjectCopy 中的值:

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

let testObjectCopy = testObject;

testObject.a = 9;
console.log(testObjectCopy.a);

尽管引用了 testObjectCopy 而不是 testObject,但此 console.log 语句将 9 打印到控制台。 这是因为创建新变量 testObjectCopy 不会创建 testObject 副本。 相反,它引用了 testObject。 您对原始对象所做的任何更改都将反映在假定的副本中,反之亦然。

将对象重新分配给新变量只会创建原始对象的浅表副本。 在下一步中,您将探索循环对象如何成为创建深层副本的可能解决方案。

第 2 步 – 通过对象循环创建浅拷贝

遍历对象并将每个属性复制到新对象似乎是一个可行的解决方案。 要对此进行测试,请创建一个名为 copyObject 的函数,该函数接受一个名为 object 的参数:

const copyObject = object => {
  
};

copyObject 中,声明一个名为 copiedObj 的变量,该变量将保存一个空对象:

const copyObject = object => {

  let copiedObj = {};
};

object 中的每个键创建一个 for 循环。 将 copiedObj 中的键/值对设置为等于 object 中的键/值对:

const copyObject = object => {
  let copiedObj = {};

  for (let key in object) {
    copiedObj[key] = object[key];
  }
};

对于此 copyObject 函数的最后一步,返回 copiedObj

const copyObject = object => {
  let copiedObj = {};

  for (let key in object) {
    copiedObj[key] = object[key];
  }

  return copiedObj;
};

copyObject 就位后,创建一个名为 testObject 的对象并将其作为 copyObject 的参数传入:

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

要查看 copyObject 函数的结果,请使用 console.log 查看打印到控制台的 copyObject(testObject) 的输出:

console.log(copyObject(testObject));

这将产生以下输出:

Output{ a: 5, b: 6, c: { d: 4 } }

似乎循环通过 testObject 创建副本产生了预期的结果。 但是有几个原因导致这种方法无法为您提供所需的结果:

  • 将每个属性复制到新对象的循环只会复制对象上的可枚举属性。 Enumerable properties 是显示在 for 循环和 Object.keys 中的属性。
  • 复制的对象有一个新的 Object.prototype 方法,这不是您复制对象时想要的。 这意味着您对原始对象所做的任何更改都将反映在复制的对象中。
  • 如果您的对象具有作为对象的属性,则您复制的对象实际上将引用原始对象,而不是创建实际副本。 这意味着如果您更改复制对象中的嵌套对象,原始对象也会更改。
  • 不会复制任何属性描述符。 如果将 configurablewritable 设置为 false,则复制对象中的属性描述符将默认为 true

循环对象允许您创建浅拷贝,但无法使用此方法创建深拷贝。 值得庆幸的是,有一个库可以提供创建深层副本的解决方案。

第 3 步 – 使用 Lodash 创建浅拷贝和深拷贝

对于仅存储原始类型(如数字和字符串)的简单对象,上述浅拷贝方法将起作用。 但是,如果您的对象具有对其他嵌套对象的引用,则不会复制实际对象。 您只会复制参考。

对于深拷贝,一个不错的选择是使用可靠的外部库,例如 Lodash。 Lodash 是一个库,它提供了两种不同的功能,允许您进行浅拷贝和深拷贝。 它们是 cloneclonedeep

要测试 Lodash cloneclonedeep 功能,您需要先安装 Lodash:

npm install --save lodash

现在安装了 lodash,使用 require() 函数现在可以访问 Lodash 也提供的所有函数:

const  _ = require('lodash');

现在您可以在代码中使用 cloneclonedeep 函数。 创建一个名为 externalObject 的对象。 给出一个值为 'Gator' 的键 animal

const externalObject = {
  animal: 'Gator'
};

创建另一个名为 originalObject 的对象。 originalObject 将存储七个具有不同值的属性。 属性 d 引用了 externalObject,它具有 animal 的属性和 'Gator' 的值。

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

使用 clone 创建浅拷贝

声明一个常量变量 shallowClonedObject 并使用 Lodash clone 函数将其分配给 originalObject 的浅表副本:

const shallowClonedObject = _.clone(originalObject);

externalObject 中重新分配 animal 键的值。 将其设置为 'Crocodile'。 使用两个 console.log 语句将 originalObjectshallowClonedObject 都打印到屏幕上:

externalObject.animal = 'Crocodile';

console.log(originalObject);
console.log(shallowClonedObject);

此代码的输出将如下所示:

Output{ a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } }
{ a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } }

externalObject 中的 animal 属性分配给新值将同时更改 originalObjectshallowClonedObjectconsole.log 语句将显示这一点。 发生这种情况是因为浅克隆只能复制对 externalObject 的引用,而不是创建一个全新的对象。

使用 clonedeep 创建深层副本

您可以使用 Lodash clonedeep 函数创建深层副本:

const deepClonedObject = _.clonedeep(originalObject);

deepClonedObject 就位后,将 externalObject 中的 animal 键的值重新分配为等于 'Lizard'

同样,使用两个 console.log 语句将 originalObjectdeepClonedObject 打印到屏幕上:

externalObject.animal = 'Lizard';

console.log(originalObject);
console.log(deepClonedObject);

此代码的输出将如下所示:

Output{ a: 1, b: 'string', c: false, d: { animal: 'Lizard' } }
{ a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } }

originalObject 中的 'animal' 属性发生了变化,但对于 deepClonedObject,它保持为 'Crocodile',因为整个对象是单独复制的,而不是复制引用。 使用 clonedeep 函数可以成功创建对象的深层副本。

结论

了解如何在 JavaScript 中深度克隆对象很重要。 您通过重新分配和循环对象来创建对象的浅表副本。 您还使用了 Lodash 库来创建对象的浅拷贝和深拷贝。

如果你想了解更多关于 JavaScript 中的对象,这个 Understanding Objects in JavaScript 教程是一个很好的起点。 如果您想更进一步并学习如何复制对象方法,Copying Objects in JavaScript 文章可以为您指明正确的方向。