JavaScript中对象深度克隆的最高效方法

JavaScript中对象深度克隆的最高效方法

技术背景

在JavaScript编程中,对象克隆是一个常见的需求。浅克隆只复制对象的一层属性,而深度克隆会递归地复制对象的所有属性,包括嵌套对象。不同的克隆方法适用于不同的场景,了解它们的优缺点对于编写高效、可靠的代码至关重要。

实现步骤

原生深度克隆

现在所有主流浏览器和Node.js >= 17都支持structuredClone(value)函数,对于旧系统也有polyfills可用。

1
structuredClone(value);

如果需要,可先加载polyfill:

1
import structuredClone from '@ungap/structured-clone';

不过,该方法有一些限制:

  • 函数对象无法通过结构化克隆算法复制,尝试复制会抛出DataCloneError异常。
  • 克隆DOM节点同样会抛出DataCloneError异常。
  • 某些对象属性不会被保留,如RegExp对象的lastIndex属性、属性描述符、setter、getter等。

有数据丢失的快速克隆 - JSON.parse/stringify

如果对象中不包含Date、函数、undefinedInfinityRegExpMapSetBlobFileListImageData、稀疏数组、类型化数组或其他复杂类型,可使用JSON.parse(JSON.stringify(object))进行深度克隆。

1
2
3
4
5
6
7
8
9
10
11
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // 会被字符串化
undef: undefined, // 会丢失
inf: Infinity, // 会被强制转换为 'null'
re: /.*/, // 会丢失
};
const clone = JSON.parse(JSON.stringify(a));

使用库进行可靠克隆

许多主流库都提供了对象克隆的函数,如:

  • Lodash - cloneDeep:如果未使用其他提供深度克隆函数的库,这可能是最佳选择。
1
2
3
const _ = require('lodash');
const original = { a: { b: 1 } };
const clone = _.cloneDeep(original);
  • Ramda - clone
  • AngularJS - angular.copy
  • jQuery - jQuery.extend(true, { }, oldObject)
  • just library - just-clone

单行程代码的非深度克隆

Object.assign方法可用于浅克隆对象:

1
2
const obj = { a: 1, b: 2 };
const clone = Object.assign({}, obj);

对于不支持Object.assign的旧浏览器,可使用以下polyfill:

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
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function(target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}

var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);

var keysArray = Object.keys(nextSource);
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
}
});
}

数组的深度克隆

根据数组元素的类型不同,有不同的深度克隆方法:

  • 仅包含原始值的数组:可使用扩展运算符...slice()splice(0)concat()等。
1
2
3
let arr1a = [1, 'a', true];
let arr1b = [...arr1a];
let arr1d = arr1a.slice();
  • 包含原始值和对象字面量的数组:可使用JSON.parse(JSON.stringify())
1
2
let arr2a = [1, 'a', true, {}, []];
let arr2b = JSON.parse(JSON.stringify(arr2a));
  • 包含原始值、对象字面量和原型的数组:可编写自定义函数或使用第三方工具函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function copy(aObject) {
let bObject = Array.isArray(aObject) ? [] : {};

let value;
for (const key in aObject) {
value = aObject[key];
bObject[key] = (typeof value === "object") ? copy(value) : value;
}

return bObject;
}

let arr3a = [1, 'a', true, {}, [], new Object()];
let arr3b = copy(arr3a);

最佳实践

  • 如果对象不包含复杂类型,且对性能要求较高,可优先使用JSON.parse(JSON.stringify())
  • 如果需要处理复杂类型(如DateRegExp等),建议使用structuredClone或第三方库(如Lodash的cloneDeep)。
  • 在性能敏感的场景下,可编写自定义的克隆函数。

常见问题

  • JSON.parse(JSON.stringify())的局限性:该方法会丢失Date、函数、undefinedInfinityRegExp等类型的数据,且不能处理循环引用。
  • Object.assign的浅克隆问题Object.assign只进行浅克隆,对于嵌套对象,修改原对象的嵌套属性会影响克隆对象。
  • 结构化克隆的限制:结构化克隆不能复制函数对象和DOM节点,且某些对象属性不会被保留。

JavaScript中对象深度克隆的最高效方法
https://119291.xyz/posts/2025-05-08.most-efficient-way-to-deep-clone-object-in-javascript/
作者
ww
发布于
2025年5月8日
许可协议