如何正确克隆 JavaScript 对象

如何正确克隆 JavaScript 对象

技术背景

在 JavaScript 中,对象是引用类型。当我们将一个对象赋值给另一个变量时,实际上是复制了对象的引用,而不是对象本身。这意味着对新变量的修改会影响原始对象。在某些情况下,我们需要创建一个对象的副本,使得对副本的修改不会影响原始对象,这就需要进行对象克隆。

实现步骤

浅拷贝

浅拷贝只复制对象的一层属性,如果对象的属性是引用类型,则只复制引用,而不是对象本身。

1. 使用 Object.assign()

1
2
const original = { a: 1, b: { c: 2 } };
const shallowClone = Object.assign({}, original);

2. 使用扩展运算符(Spread Operator)

1
2
const original = { a: 1, b: { c: 2 } };
const shallowClone = { ...original };

深拷贝

深拷贝会递归地复制对象的所有属性,包括嵌套对象,从而创建一个完全独立的对象副本。

1. 使用 JSON.parse(JSON.stringify())

1
2
const original = { a: 1, b: { c: 2 } };
const deepClone = JSON.parse(JSON.stringify(original));

注意:该方法有局限性,它会忽略 undefinedfunctionSymbol 等属性,并且不能处理循环引用的对象。

2. 自定义递归函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function clone(obj) {
if (null == obj || "object" != typeof obj) return obj;

if (obj instanceof Date) {
const copy = new Date();
copy.setTime(obj.getTime());
return copy;
}

if (obj instanceof Array) {
const copy = [];
for (let i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}

if (obj instanceof Object) {
const copy = {};
for (const attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}

throw new Error("Unable to copy obj! Its type isn't supported.");
}

const original = { a: 1, b: { c: 2 } };
const deepClone = clone(original);

3. 使用 structuredClone()

1
2
const original = { a: 1, b: { c: 2 } };
const deepClone = structuredClone(original);

注意structuredClone() 是一个新的 JavaScript 标准,浏览器兼容性需要检查。

使用第三方库

1. jQuery

1
2
3
4
5
const original = { a: 1, b: { c: 2 } };
// 浅拷贝
const shallowClone = jQuery.extend({}, original);
// 深拷贝
const deepClone = jQuery.extend(true, {}, original);

2. Lodash

1
2
3
4
5
6
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
// 浅拷贝
const shallowClone = _.clone(original);
// 深拷贝
const deepClone = _.cloneDeep(original);

最佳实践

  • 浅拷贝:如果对象只有一层属性或者不需要对嵌套对象进行独立修改,使用 Object.assign() 或扩展运算符进行浅拷贝,它们简洁高效。
  • 深拷贝
    • 如果对象不包含 undefinedfunctionSymbol 等特殊属性,且没有循环引用,可以使用 JSON.parse(JSON.stringify())
    • 如果需要处理复杂对象,包括日期、数组等,自定义递归函数是一个不错的选择。
    • 如果浏览器支持,structuredClone() 是最简单的深拷贝方法。
    • 如果项目中已经使用了 jQuery 或 Lodash 等库,使用它们提供的克隆方法更方便。

常见问题

循环引用问题

使用 JSON.parse(JSON.stringify()) 和简单的递归函数会遇到循环引用问题,导致错误。可以使用带有缓存的递归函数来解决,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function cloneWithCache(obj) {
const cache = new WeakMap();

function copy(object) {
if (typeof object !== 'object' || object === null) return object;

if (cache.has(object)) return cache.get(object);

let result;
if (object instanceof Date) {
result = new Date(object.getTime());
} else if (object instanceof Array) {
result = [];
for (const item of object) {
result.push(copy(item));
}
} else {
result = {};
for (const key in object) {
if (object.hasOwnProperty(key)) {
result[key] = copy(object[key]);
}
}
}

cache.set(object, result);
return result;
}

return copy(obj);
}

const circularObj = {};
circularObj.self = circularObj;
const clonedCircularObj = cloneWithCache(circularObj);

性能问题

JSON.parse(JSON.stringify()) 涉及字符串的序列化和反序列化,性能较低,不适合处理大型对象。在性能敏感的场景下,建议使用自定义递归函数或第三方库的克隆方法。


如何正确克隆 JavaScript 对象
https://119291.xyz/posts/2025-04-22.how-to-correctly-clone-a-javascript-object/
作者
ww
发布于
2025年4月22日
许可协议