DailyENJS 致力于翻译优秀的前端英文技术文章,为技术同学带来更好的技术视野。
因为在JavaScript中的对象是引用值,所以不能简单地使用 =
进行复制。但不用担心,这里有3种方法可以克隆一个对象?
const food = { beef: '?', bacon: '?' }
// "Spread"
{ ...food }
// "Object.assign"
Object.assign({}, food)
// "JSON"
JSON.parse(JSON.stringify(food))
// RESULT:
// { beef: '?', bacon: '?' }
对象都是引用类型
你的第一个问题可能是,为什么我不能用 =
?我们看看如果这样做会发生什么:
const obj = {one: 1, two: 2};
const obj2 = obj;
console.log(
obj, // {one: 1, two: 2};
obj2 // {one: 1, two: 2};
)
到目前为止,这两个对象似乎输出相同的东西。没问题,对吧。但是让我们看看如果我们编辑第二个对象会发生什么:
const obj2.three = 3;
console.log(obj2);
// {one: 1, two: 2, three: 3}; <-- ✅
console.log(obj);
// {one: 1, two: 2, three: 3}; <-- ?
WTH?我改变了obj2,但为什么obj也受到了影响。那是因为对象是引用类型。所以当你使用 =
时,它会将指针复制到它占用的内存空间。引用类型不包含值,它们是指向内存中值的指针。
如果您想了解更多相关信息,请查看 Gordon Zhu Watch and Code。可以免费注册并观看视频 “Comparison with objects”。他给出了一个超级棒的解释。
使用扩展运算符
使用使用扩展运算符会克隆你的对象。请注意,这将是一个浅复制。戒指本文,用于克隆对象的扩展运算符处于 stage 4。因此,它尚未正式出现在规范中。因此,如果您要使用它,则需要使用Babel(或类似的东西)进行编译。
const food = { beef: '?', bacon: '?' };
const cloneFood = { ...food };
console.log(cloneFood);
// { beef: '?', bacon: '?' }
使用 Object.assign
另外,Object.assign
已经是规范并且可以创建一个浅拷贝副本。
const food = { beef: '?', bacon: '?' };
const cloneFood = Object.assign({}, food);
console.log(cloneFood);
// { beef: '?', bacon: '?' }
使用 JSON
最后的这个方法是一个深拷贝,现在我要提一下,这是一种深拷贝对象的快速但 dirty 的方法。对于更强大的解决方案,我建议使用像 lodash 这样的东西
const food = { beef: '?', bacon: '?' };
const cloneFood = JSON.parse(JSON.stringify(food))
console.log(cloneFood);
// { beef: '?', bacon: '?' }
Lodash DeepClone VS JSON
下面是来自社区的评论。是的,这是我之前的文章 How to Deep Clone an Array。但这个想法依旧适用于对象
Alfredo Salzillo: 我想请你注意,deepClone
和 JSON.stringify / parse
之间存在一些差异
-
JSON.stringify/parse
: 仅对数字、字符串和不含函数和 Symble 属性的对象有效 -
deepClone
: 对所有类型有效,函数和 Symble 会通过引用复制
例子:
const lodashClonedeep = require("lodash.clonedeep");
const arrOfFunction = [() => 2, {
test: () => 3,
}, Symbol('4')];
// deepClone copy by refence function and Symbol
console.log(lodashClonedeep(arrOfFunction));
// JSON replace function with null and function in object with undefined
console.log(JSON.parse(JSON.stringify(arrOfFunction)));
// function and symbol are copied by reference in deepClone
console.log(lodashClonedeep(arrOfFunction)[0] === lodashClonedeep(arrOfFunction)[0]);
console.log(lodashClonedeep(arrOfFunction)[2] === lodashClonedeep(arrOfFunction)[2]);
@OlegVaraksin: JSON方法存在循环依赖的问题。此外,克隆对象中的属性顺序可能不同。
浅拷贝 vs 深拷贝
当我使用拓展运算符 ...
复制一个对象时,我只创建一个浅拷贝。如果数组是嵌套的或多维的,它将无法工作。让我们来看看:
const nestedObject = {
country: '??',
{
city: 'vancouver'
}
};
const shallowClone = { ...nestedObject };
// Changed our cloned object
clonedNestedObject.country = '??'
clonedNestedObject.country.city = 'taipei';
我们通过改变 city
改变了克隆对象。我们来看看输出。
console.log(shallowClone);
// {country: '??', {city: 'taipei'}} <-- ✅
console.log(nestedObject);
// {country: '??', {city: 'taipei'}} <-- ?
浅拷贝意味着复制第一级,更深级别的则是引用。
深拷贝
我们采用相同的示例,但使用 JSON
进行深拷贝
const deepClone = JSON.parse(JSON.stringify(nestedObject));
console.log(deepClone);
// {country: '??', {city: 'taipei'}} <-- ✅
console.log(nestedObject);
// {country: '??', {city: 'vancouver'}} <-- ✅
如您所见,深拷贝的副本是嵌套对象的真实副本。通常浅拷贝已经足够好,你真的不需要深拷贝。它就像钉枪和锤子。
大多数时候,锤子非常精细。使用钉枪进行一些小型的艺术和工艺往往是一种矫枉过正,锤子就好了。这一切都是为了正确的工作使用正确的工具?
性能
不幸的是,我不能为扩展运算符做一个测试,因为它尚未正式出现在规范中。不过,我把它包含在测试中,所以你可以在将来运行它?。但结果显示 Object.assign
比 JSON
快很多。
社区意见
Object.assign vs Spread
@d9el: 值得注意的是,Object.assign
是一个修改并返回目标对象的函数。在Samantha的例子中使用以下内容:
const cloneFood = Object.assign({}, food)
{}
是被修改的对象。目标对象在上面未被任何变量引用,但由于 Object.assign
返回目标对象,因此我们能够将生成的已分配对象存储到 cloneFood
变量中。我们可以切换我们的示例并使用以下内容:
const food = { beef: '?', bacon: '?' };
Object.assign(food, { beef: '?' });
console.log(food);
// { beef: '?', bacon: '?' }
显然,我们 food
对象中 beef
的值是错误的,所以我们可以使用 Object.assign
分配正确的 beef
值。我们实际上根本没有使用函数的返回值,但是我们正在修改我们用变量 food
引用的目标对象。
另一方面,扩展运算符是一个将一个对象的属性复制到一个新对象中的运算符。如果我们想使用拓展运算符来复制上面的例子来修改我们的变量food
const food = { beef: '?', bacon: '?' };
food = {
...food,
beef: '?',
}
// TypeError: invalid assignment to const `food'
我们得到一个错误,因为我们在创建新对象时使用拓展运算符,因此将一个全新的对象分配给用const声明的food
,这是非法的。所以我们可以选择声明一个新变量来保存我们的新对象,如下所示:
const food = { beef: '?', bacon: '?' };
const newFood = {
...food,
beef: '?',
}
console.log(newFood);
// { beef: '?', bacon: '?' }
或者我们可以使用let或var声明 food
,这将允许我们分配一个全新的对象。
let food = { beef: '?', bacon: '?' };
food = {
...food,
beef: '?',
}
console.log(food);
// { beef: '?', bacon: '?' }
使用外部库进行深拷贝
@lesjeuxdebebel:使用 jquery 的 $.extend()
函数
@edlinkiii: underscore.js _.clone()
@Percy_Burton: lodash的 cloneDeep 方法
更多使用JavaScript的方法
@hariharan_d3v: Object.fromEntries(Object.entries(food))
浅拷贝对象
资料
- MDN Web Docs: Object.assign
- Stack Overflow: What is the most efficient way to deep clone an object in JavaScript?
- 2ality: Rest/Spread Properties
- Stack Overflow: Object spread vs. Object.assign
原文地址: https://medium.com/dailyjs/3-ways-to-clone-objects-in-javascript-22deed66f39d
最后照旧是一个广告贴,最近新开了一个分享技术的公众号,欢迎大家关注?(目前关注人数可怜?)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。