7

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: 我想请你注意,deepCloneJSON.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.assignJSON 快很多。

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)) 浅拷贝对象

资料

原文地址: https://medium.com/dailyjs/3-ways-to-clone-objects-in-javascript-22deed66f39d

最后照旧是一个广告贴,最近新开了一个分享技术的公众号,欢迎大家关注?(目前关注人数可怜?)


灰风GreyWind
308 声望13 粉丝

灰风也叫忽如寄