[译文]如何用JavaScript克隆一个对象

Published on:
Tags: JavaScript


JavaScript对象是键值对的集合。它是一种可以包含各种数据类型的非原生的数据类型。

1
2
3
4
5
const userDetails = {
name: "John Doe",
age: 14,
verified: false
};

在 JavaScript 中使用对象时,有时你可能想要更改值或向对象添加新属性。

在某些情况下,在更新或添加新属性之前,你会希望创建一个新对象并复制或克隆原始对象的值。

例如,如果你想复制 userDetails 对象的值,然后修改 name 属性的值。此时,你会使用赋值 (=) 运算符。

1
2
const newUser = userDetails;
console.log(newUser); // {name: 'John Doe', age: 14, verified: false}

一切看起来都正常,但是当我们修改一下新对象,看看会发生什么?

1
2
3
4
5
const newUser = userDetails;
newUser.name = "Jane Doe";

console.log(newUser); // {name: 'Jane Doe', age: 14, verified: false}
console.log(userDetails); // {name: 'Jane Doe', age: 14, verified: false}

结果就是原始对象 userDetails 的值也受到了影响,因为对象是引用类型。
这意味着存储在新对象或原始对象中的任何值都指向同一个对象。

这不是你想要的。你希望将一个对象的值存储在一个新对象中,并在不影响原始对象的情况下操作新对象中的值。

在本文中,你将学习三种可用于执行此操作的方法。你还将了解深克隆和浅克隆的含义以及它们的工作原理。

如果你赶时间,以下有三种方法和它们的例子。

1
2
3
4
5
6
7
8
9
// Spread Method
let clone = { ...userDetails }

// Object.assign() Method
let clone = Object.assign({}, userDetails)

// JSON.parse() Method
let clone = JSON.parse(JSON.stringify(userDetails))

如果你不赶时间,让我们开始吧。

如何使用扩展运算符在 JavaScript 中克隆对象#

扩展运算符是在 ES6 中引入的,可以将值扩展到三个点前面的对象中。

1
2
3
4
5
6
7
8
9
10
11
// Declaring Object
const userDetails = {
name: "John Doe",
age: 14,
verified: false
};

// Cloning the Object with Spread Operator
let cloneUser = { ...userDetails };

console.log(cloneUser); // {name: 'John Doe', age: 14, verified: false}

这不再被引用,这意味着更改新对象的值不会影响原始对象。

1
2
3
4
5
6
7
8
9
// Cloning the Object with Spread Operator
let cloneUser = { ...userDetails };

// changing the value of cloneUser
cloneUser.name = "Jane Doe"

console.log(cloneUser.name); // 'Jane Doe'
console.log(cloneUser); // {name: 'Jane Doe', age: 14, verified: false}
console.log(userDetails); // {name: 'John Doe', age: 14, verified: false}

当你检查原始对象的 name 属性或整个对象中的值时,你会注意到它没有受到影响。

注意:当遇到深对象时,扩展运算符只能对对象进行浅拷贝,当你读完本文你就会明白了。

如何使用 Object.assign() 在 JavaScript 中克隆对象#

扩展运算符的替代方法是 Object.assign() 方法。你可以使用此方法将值和属性从一个或多个源对象复制到目标对象。

1
2
3
4
5
6
7
8
9
10
11
// Declaring Object
const userDetails = {
name: "John Doe",
age: 14,
verified: false
};

// Cloning the Object with Object.assign() Method
let cloneUser = Object.assign({}, userDetails);

console.log(cloneUser); // {name: 'John Doe', age: 14, verified: false}

这不再被引用,这意味着更改新对象的值不会影响原始对象。

1
2
3
4
5
6
7
8
9
// Cloning the Object with Object.assign() Method
let cloneUser = Object.assign({}, userDetails);

// changing the value of cloneUser
cloneUser.name = "Jane Doe"

console.log(cloneUser.name); // 'Jane Doe'
console.log(cloneUser); // {name: 'Jane Doe', age: 14, verified: false}
console.log(userDetails); // {name: 'John Doe', age: 14, verified: false}

当你检查原始对象的 name 属性或整个对象中的值时,你会注意到它没有受到影响。

注意:当遇到深对象时,Object.assign() 方法只能对对象进行浅拷贝,当你读完本文你就会明白了。

如何使用 JSON.parse() 在 JavaScript 中克隆对象#

最后一个方法是 JSON.parse()。你将结合JSON.stringify() 一起使用。
你可以使用它来深度克隆,但它有一些缺点。
首先,让我们看看它是如何工作的。

1
2
3
4
5
6
7
8
9
10
11
// Declaring Object
const userDetails = {
name: "John Doe",
age: 14,
verified: false
};

// Cloning the Object with JSON.parse() Method
let cloneUser = JSON.parse(JSON.stringify(userDetails));

console.log(cloneUser); // {name: 'John Doe', age: 14, verified: false}

就像前面的方法一样,新的对象不再引用它。这意味着你可以在不影响原始对象的情况下更改新对象中的值。

1
2
3
4
5
6
7
8
9
// Cloning the Object with JSON.parse() Method
let cloneUser = JSON.parse(JSON.stringify(userDetails));

// changing the value of cloneUser
cloneUser.name = "Jane Doe"

console.log(cloneUser.name); // 'Jane Doe'
console.log(cloneUser); // {name: 'Jane Doe', age: 14, verified: false}
console.log(userDetails); // {name: 'John Doe', age: 14, verified: false}

当你检查原始对象的 name 属性或整个对象中的值时,你会注意到它没有受到影响。

注意:此方法可用于深度克隆,但不是最佳选择,因为它不适用于function或symbol属性。

现在让我们探讨浅克隆和深度克隆,以及如何使用该JSON.parse()方法执行深度克隆。你还将了解为什么它不是最佳的选择。

浅克隆与深克隆#

到目前为止,本文使用的示例是一个只有一层的基础对象。
这意味着我们只执行了浅克隆。
但是当一个对象有多于一层时,你就需要进行深度克隆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Shallow object 浅对象
const userDetails = {
name: "John Doe",
age: 14,
verified: false
};

// Deep object 深对象
const userDetails = {
name: "John Doe",
age: 14,
status: {
verified: false,
}
};

注意,深对象不止一层,因为 userDetails 对象中还有另外一个对象。
一个深对象可以有任意多的层次。

注意:当你使用扩展操作符或Object.assign()方法克隆一个深对象时,更深的对象将引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const userDetails = {
name: "John Doe",
age: 14,
status: {
verified: false
}
};

// Cloning the Object with Spread Operator
let cloneUser = { ...userDetails };

// Changing the value of cloneUser
cloneUser.status.verified = true;

console.log(cloneUser); // {name: 'John Doe', age: 14, status: {verified: true}}
console.log(userDetails); // {name: 'John Doe', age: 14, status: {verified: true}}

你会注意到原始对象和新对象都会受到影响,因为当你使用扩展运算符或Object.assign()方法克隆深对象时,将引用更深的对象。

你怎么解决这个问题#

你可以使用JSON.parse()方法,一切都会正常进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const userDetails = {
name: "John Doe",
age: 14,
status: {
verified: false
}
};

// Cloning the Object with Spread Operator
let cloneUser = JSON.parse(JSON.stringify(userDetails));

// Changing the value of cloneUser
cloneUser.status.verified = true;

console.log(cloneUser); // {name: 'John Doe', age: 14, status: {verified: true}}
console.log(userDetails); // {name: 'John Doe', age: 14, status: {verified: false}}

但是这种方法有一个问题 —— 就是你可能会丢失数据。

正如以上的例子显示的那样,JSON.stringify()与数字、字符串或布尔值等原生的数据类型配合得很好。
相对应的,当遇到非原生的数据类型时,JSON.stringify()方法出现意想不到的结果。

例如,当属性的值为:Nan、Infinityto、null、undefined、Symbol、object等类型是,JSON.stringify()方法会返回一个空的键值对并跳过它。

1
2
3
4
5
6
7
8
9
10
11
12
const userDetails = {
name: "John Doe",
age: 14,
status: {
verified: false,
method: Symbol(),
title: undefined
}
};

// Cloning the Object with Spread Operator
let cloneUser = JSON.parse(JSON.stringify(userDetails));

JSON.stringify() 将不会返回值为 Symbol 和 undefined 的键值对。

1
2
3
4
5
6
7
8
9
10
console.log(cloneUser); 

// Output
{
name: "John Doe",
age: 14,
status: {
verified: false
}
};

这意味着你需要小心。实施深克隆的最佳选择是使用 Lodash
这样你就可以确定你的任何数据都不会丢失。

1
2
3
4
5
6
7
8
9
10
11
const userDetails = {
name: "John Doe",
age: 14,
status: {
verified: false,
method: Symbol(),
title: undefined
}
};

console.log(_.cloneDeep(userDetails));

总结#

本文介绍了如何使用三种主要方法在 JavaScript 中克隆对象。
你已经了解了这些方法的工作原理,以及何时使用每种方法。
你还了解了深度克隆。

你可以阅读本文以了解为什么JSON.parse(JSON.stringify())在 JavaScript 中克隆对象是一种不好的做法。

原文 JS Copy an Object – How to Clone an Obj in JavaScript