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
快很多。
Performance Test
社区意见
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
最后照旧是一个广告贴,最近新开了一个分享技术的公众号,欢迎大家关注????(目前关注人数可怜????)