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

介绍

如果你计划使用JavaScript编码,你需要了解对象是如何工作的。

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

前提条件

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

步骤 1 – 通过重新分配对象创建 Shallow 副本

如果您创建一个包含对象并更改该对象的函数,则可能需要创建对象的副本并更改副本,而不是突变原始对象。

初始化一个新对象,并将其分配到变量testObject中,该对象应有字母a,bc作为键,以及1,23作为值。

在 JavaScript 中创建对象:

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

现在,尝试通过将testObject分配给一个名为testObjectCopy的新变量来创建该对象的副本来操纵:

1let testObject = {
2  a: 1,
3  b: 2,
4  c: 3
5};
6
7let testObjectCopy = testObject;

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

1let testObject = {
2  a: 1,
3  b: 2,
4  c: 3
5};
6
7let testObjectCopy = testObject;
8
9testObject.a = 9;

您可以期望此更改仅反映在testObject对象中。 使用console.log语句来检查testObjectCopy中的a值是什么:

 1let testObject = {
 2  a: 1,
 3  b: 2,
 4  c: 3
 5};
 6
 7let testObjectCopy = testObject;
 8
 9testObject.a = 9;
10console.log(testObjectCopy.a);

console.log声明将打印9到控制台,尽管引用testObjectCopy,而不是testObject。 这是因为创建新的变量testObjectCopy不会创建testObject副本,而是引用testObject

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

步骤 2 – 通过环绕对象创建 Shallow 副本

浏览对象并将每个属性复制到一个新的对象似乎是一个可行的解决方案. 要测试这一点,请创建一个名为copyObject的函数,该函数采用一个名为object的参数:

1const copyObject = object => {
2
3};

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

1const copyObject = object => {
2
3  let copiedObj = {};
4};

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

1const copyObject = object => {
2  let copiedObj = {};
3
4  for (let key in object) {
5    copiedObj[key] = object[key];
6  }
7};

对于这个copyObject函数的最后一步,返回copiedObj:

1const copyObject = object => {
2  let copiedObj = {};
3
4  for (let key in object) {
5    copiedObj[key] = object[key];
6  }
7
8  return copiedObj;
9};

有了copyObject,创建一个名为testObject的对象,然后将其传输为copyObject的参数:

 1const copyObject = object => {
 2
 3  let copiedObj = {};
 4
 5  for (let key in object) {
 6    copiedObj[key] = object[key];
 7  }
 8
 9  return copiedObj;
10};
11
12const testObject = {
13  a: 5,
14  b: 6,
15  c: {
16    d: 4
17  }
18};
19
20copyObject(testObject);

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

1console.log(copyObject(testObject));

这将产生这样的产量:

1[secondary_label Output]
2{ a: 5, b: 6, c: { d: 4 } }

它可能看起来像是通过testObject循环创建一个副本产生了所需的结果,但有几个原因,为什么这种方法不会给你你正在寻找的结果:

  • 将每个属性复制到新对象的循环只会复制可编号的属性在对象上。 _ 可编号的属性_是将出现在for循环和Object.keys中的属性 *复制对象有一个新的Object.prototype方法,而不是你在复制对象时想要的属性。这意味着你对原始对象所做的任何更改都会反映在复制对象中
  • 如果你的对象有一个属性是对象,那么你的复制对象实际上将指向原始而不是创建实际副本。

通过对象旋转可以创建浅副本,但无法使用此方法创建深副本. 幸运的是,有一个库可提供创建深副本的解决方案。

步骤 3 – 使用 Lodash 创建 Shallow 和 Deep 副本

对于仅存储原始类型(如数字和字符串)的简单对象,如上面的复制方法将奏效,但是,如果您的对象具有与其他嵌入对象的引用,则实际对象不会被复制。

对于深度复制,一个很好的选择是使用可靠的外部库,如 Lodash。Lodash是一个提供两个不同的功能的库,允许您进行浅层复制和深层复制。

要测试Lodash的克隆克隆功能,您需要先安装Lodash:

1npm install --save lodash

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

1const _ = require('lodash');

现在你可以在你的代码中使用克隆克隆函数,创建一个名为externalObject的对象,给一个具有Gator值的动物密钥:

1const externalObject = {
2  animal: 'Gator'
3};

创建另一个名为originalObject的对象,originalObject将存储7个具有不同值的属性,每个属性d是指具有动物属性和Gator值的外部Object

1const originalObject = {
2  a: 1,
3  b: 'string',
4  c: false,
5  d: externalObject
6};

使用克隆创建 Shallow 副本

声明恒定变量shallowClonedObject,并使用Lodash克隆函数将其分配给原始Object的浅副本:

1const shallowClonedObject = _.clone(originalObject);

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

1externalObject.animal = 'Crocodile';
2
3console.log(originalObject);
4console.log(shallowClonedObject);

这个代码的输出将是这样的:

1[secondary_label Output]
2{ a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } }
3{ a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } }

外部对象中的动物属性分配到一个新的值将改变原始对象shallowClonedObject

使用clonedeep创建深度副本

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

1const deepClonedObject = _.clonedeep(originalObject);

當「deepClonedObject」位於位置時,將「外部Object」中的「動物」鍵的值重新分配為「Lizard」。

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

1externalObject.animal = 'Lizard';
2
3console.log(originalObject);
4console.log(deepClonedObject);

这个代码的输出将是这样的:

1[secondary_label Output]
2{ a: 1, b: 'string', c: false, d: { animal: 'Lizard' } }
3{ a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } }

原始对象中的动物属性发生变化,但对于 深克隆对象,它仍然是鳄鱼,因为整个对象被单独复制而不是复制引用。

结论

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

如果你想了解更多关于对象的JavaScript,这个 理解对象的JavaScript教程是一个很好的开始的地方。

Published At
Categories with 技术
Tagged with
comments powered by Disqus