首先,让我们澄清浅克隆和深克隆之间的区别:
浅克隆是将其基本属性克隆,但它的引用属性仍然引用原始对象的克隆。让我解释一下:
let original = {
foo: "brlja",
howBigIsUniverse: Infinity,
mrMethodLookAtMe: () => "they call me mr. Method",
moo: {
moo: "MOO"
}
};
let shallow = Object.assign({}, original);
console.log(original, shallow);
shallow.moo.moo = "NOT MOO";
console.log(original, shallow);
注意如何改变浅复制的非原始属性的内部属性反映在原始对象上。
那为什么我们要使用浅复制?
什么时候会使用浅复制?
- 你的对象所有属性都是原始值
- 你正在进行部分复制,其中所有复制的属性都是原始值
- 您不关心原始对象的命运(是否有理由复制而不是改用该对象?)
好了,让我们开始制作适当的(深度)复制。 深层复制应将原始对象按值复制到克隆体中,并且随着我们深入对象,这种复制应该持续存在。 因此,如果我们在原始属性内部有X级深嵌套对象,则它仍应是副本而不是对内存中相同事物的引用。
大多数人建议滥用JSON API。 他们认为将对象转换为字符串,然后通过它再次转换回对象将产生深层副本。 好吧,是的和不。 让我们试图做到这一点。
扩展我们的原始示例:
let falseDeep = JSON.parse(JSON.stringify(original))
falseDeep.moo.moo = "HEY I CAN MOO AGAIN"
console.log(original, falseDeep)
看起来没问题,对吧?错了! 看看从一开始就混入的 mrMethodLookAtMe 和 howBigIsUniverse 属性发生了什么:)
一个返回 null,绝不是 Infinity,另一个则消失了。那可不太好。
简而言之:使用 JSON API 会出现 NaN 或 Infinity 这样“更智能”的值变成 null 的问题。如果您在原始对象的属性中使用了以下内容,则会出现更多问题:方法、正则表达式、映射、集合、Blobs、FileLists、ImageData、稀疏数组、类型化数组。
为什么呢?因为这会产生一些最难以追踪的错误。 我噩梦般地追踪着消失的方法或被转换为其他类型(这通过某人的坏输入参数检查,但然后无法产生有效结果)在 TypeScript 出现之前。
是时候结束了!那正确答案是什么呢?
- 编写自己的深拷贝实现。我很喜欢你,但请不要在我们有期限要满足的情况下这样做。
- 使用项目中已经使用的库或框架提供给您的深度克隆函数。
- Lodash 的 cloneDeep
许多人仍在使用 jQuery。所以在我们的示例中(请将导入放在文件顶部):
import jQ from "jquery";
let trueDeep = jQ.extend(true, original, {});
console.log(original, trueDeep);
这个方法是有效的,它可以生成一个漂亮的深拷贝,而且只需要一行代码就能完成。但我们需要导入整个jQuery库。如果项目中已经在使用它,那就没问题了,但我通常会避免使用它,因为它太臃肿而且命名方式非常不一致。
同样地,AngularJS用户可以使用angular.copy()
。
但如果我的框架或库没有类似的函数呢?
你可以使用我个人最喜欢的JS库之一(我并未参与该项目,只是一个大粉丝)- Lodash(或者用下划线表示)。所以,在我们的例子中添加如下代码(同样要注意import的位置):
import _ from "lodash";
var fastAndDeepCopy = _.cloneDeep(objects);
console.log(original, lodashDeep);
这只是一个简单的单行代码,它能够工作,而且速度很快。
基本上就是这样:)
现在你知道了JS中浅层拷贝和深层拷贝之间的区别。你意识到JSON API滥用只是滥用,而不是真正的解决方案。如果你已经在使用jQuery或AngularJS,那么你现在知道已经有了解决方案。如果没有,你可以编写自己的代码,也可以考虑使用lodash库。
完整的示例可以在这里找到:
codesandbox - entire example
this.state.messages
是一个对象还是一个数组? - Jordan Running