js实现深拷贝的核心答案是通过递归复制对象所有层级并切断引用关系,以确保副本与原数据完全独立。最简单的方法是使用json.parse(json.stringify(obj)),适用于仅含基本类型和普通对象的“纯净”数据,但会丢失函数、undefined、symbol等,且无法处理循环引用;更通用的方案是编写递归函数,通过weakmap记录已拷贝对象来避免循环引用,并显式处理date、regexp、map、set等特殊类型;现代javascript提供了structuredclone()原生api,能高效处理多种复杂类型和循环引用,但不支持函数和dom节点;对于复杂场景或需高度定制化,推荐使用lodash的_.clonedeep()等成熟库。浅拷贝仅复制顶层引用,嵌套对象仍共享内存,修改会导致原对象变化,因此在需要完全隔离数据的场景下必须使用深拷贝。不同策略适用于不同场景:数据简单时用json方法,支持现代浏览器且不含函数时优先structuredclone(),复杂结构或需兼容旧环境时采用自定义递归函数,大型项目则推荐第三方库以保证稳定性和开发效率。

JS实现深拷贝,核心在于创建一个全新且独立的副本,不仅复制原始值,还要递归复制所有嵌套的对象和数组,确保修改副本不会影响到原始数据。这不像浅拷贝那样只复制引用,深拷贝能够彻底切断新旧数据之间的联系,让你在处理复杂数据结构时更加安心。
要实现JS的深拷贝,其实有几种主流思路,每种都有它的适用场景和局限性。
首先,最简单粗暴,也是很多人第一个想到的方法,就是利用
JSON.parse(JSON.stringify(obj))
undefined
Symbol
Date
RegExp
Date
undefined
// 简单但有局限性的深拷贝
function simpleDeepClone(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (e) {
console.warn("JSON.stringify/parse 无法处理此对象,可能存在循环引用或特殊类型。", e);
return null; // 或者抛出错误,根据需求来
}
}
// 示例
const obj1 = {
a: 1,
b: {
c: 2
},
d: new Date(),
e: function() {}
};
const clonedObj1 = simpleDeepClone(obj1);
console.log(clonedObj1);
// { a: 1, b: { c: 2 }, d: '2023-10-27T12:00:00.000Z' } - d 变成了字符串,e 没了接着,更通用、也更考验功力的方法是编写一个递归拷贝函数。这才是深拷贝的“正道”。它的核心思想就是遍历对象的每一个属性,如果属性值是基本类型,直接拷贝;如果属性值是对象或数组,就递归调用自身,直到所有嵌套层级都被复制。这里面最大的坑,也是最需要解决的问题,就是循环引用。如果对象A引用了B,B又引用了A,不处理就会陷入无限递归,栈溢出。解决办法通常是使用一个
Map
WeakMap
// 递归深拷贝,处理循环引用
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj; // null 或 非对象类型直接返回
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理特殊对象类型
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 如果还有其他需要特殊处理的内置对象,比如Map, Set, Symbol等,可以在这里添加
// 创建新对象或新数组
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj); // 存储已处理的对象,防止循环引用
// 遍历并递归拷贝属性
for (const key in obj) {
// 确保只处理对象自身的属性,而不是原型链上的
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
// 示例:处理循环引用
const original = {};
original.a = original; // 循环引用
original.b = {
c: 1
};
const clonedOriginal = deepClone(original);
console.log(clonedOriginal.a === clonedOriginal); // true,说明循环引用被正确处理,指向的是新的克隆对象中的自己
console.log(clonedOriginal.b === original.b); // false,嵌套对象被深拷贝了
// 示例:处理Date和Function
const objWithSpecialTypes = {
date: new Date(),
func: () => console.log('hello'),
reg: /abc/g,
sym: Symbol('test')
};
const clonedSpecial = deepClone(objWithSpecialTypes);
console.log(clonedSpecial.date instanceof Date); // true
console.log(clonedSpecial.date !== objWithSpecialTypes.date); // true
console.log(clonedSpecial.func === objWithSpecialTypes.func); // true,函数通常是直接引用拷贝,因为其行为通常不需克隆
console.log(clonedSpecial.reg instanceof RegExp); // true
// Symbol默认不会被枚举,所以这里不会被拷贝,除非你特殊处理
console.log(clonedSpecial.sym); // undefined最后,现代JavaScript提供了一个更原生的方案:
structuredClone()
Date
RegExp
Map
Set
ArrayBuffer
Blob
File
ImageData
// 使用 structuredClone (现代浏览器支持)
function structuredCloneWrapper(obj) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(obj);
} catch (e) {
console.warn("structuredClone 无法处理此对象,可能包含不可克隆的类型 (如函数、DOM节点)。", e);
return null;
}
} else {
console.warn("当前环境不支持 structuredClone API,请考虑使用其他深拷贝方法。");
return deepClone(obj); // 回退到自定义递归方法
}
}
// 示例
const objStructured = {
a: 1,
b: {
c: 2
},
d: new Date(),
map: new Map([
['key', 'value']
])
};
objStructured.self = objStructured; // 循环引用
const clonedStructured = structuredCloneWrapper(objStructured);
console.log(clonedStructured.d instanceof Date); // true
console.log(clonedStructured.map instanceof Map); // true
console.log(clonedStructured.self === clonedStructured); // true,循环引用处理
// clonedStructured.func = () => {}; // 如果原始对象有函数,会报错当然,你也可以选择使用成熟的第三方库,比如Lodash的
_.cloneDeep()
浅拷贝,顾名思义,它只是“表面”的拷贝。当你的对象里只有基本类型(比如数字、字符串、布尔值)时,浅拷贝确实能创建一个独立的副本,因为这些类型都是按值传递的。但一旦对象中包含了另一个对象或数组(也就是引用类型),浅拷贝就只是复制了这些嵌套对象的“引用地址”。
这意味着什么呢?打个比方,你有一份文件,浅拷贝就像是给这份文件创建了一个快捷方式。你通过快捷方式打开文件,修改了里面的内容,那么原始文件里的内容也同样被修改了,因为它们指向的是同一个实际的文件。
const originalObj = {
name: "Alice",
details: {
age: 30,
city: "New York"
},
hobbies: ["reading", "hiking"]
};
// 使用扩展运算符进行浅拷贝
const shallowCopy = { ...originalObj
};
// 修改浅拷贝中的基本类型属性
shallowCopy.name = "Bob";
console.log(originalObj.name); // 输出 "Alice" - 基本类型互不影响
// 修改浅拷贝中的嵌套对象属性
shallowCopy.details.age = 31;
console.log(originalObj.details.age); // 输出 31 - 糟糕!原始对象也被修改了
// 修改浅拷贝中的数组属性
shallowCopy.hobbies.push("coding");
console.log(originalObj.hobbies); // 输出 ["reading", "hiking", "coding"] - 再次中招!你看,当修改
shallowCopy.details.age
originalObj.details.age
shallowCopy.hobbies
originalObj.hobbies
深拷贝并不是简单地把所有东西都复制一遍就完事了,有些特殊的数据类型在处理时会带来一些挑战,或者说,它们有自己的“脾气”。
首先是函数(Functions)。通常情况下,我们深拷贝一个对象时,里面的函数属性并不会被“克隆”一份新的代码。大多数深拷贝实现,包括
JSON.parse(JSON.stringify())
structuredClone()
eval
其次是日期对象(Date objects)和正则表达式(RegExp objects)。
JSON.parse(JSON.stringify())
Date
"2023-10-27T12:00:00.000Z"
new Date(originalDate)
new RegExp(originalRegExp)
structuredClone()
再来是undefined
NaN
Infinity
JSON.stringify()
undefined
null
NaN
Infinity
JSON.stringify()
null
structuredClone()
还有Symbol
JSON.stringify()
Symbol
Symbol
for...in
Object.keys()
Symbol
Object.getOwnPropertySymbols()
Symbol
structuredClone()
Symbol
最后,也是最让人头疼的循环引用。当一个对象直接或间接引用了自身时,比如
obj.a = obj
obj1.a = obj2; obj2.b = obj1;
Map
WeakMap
Map
// 针对特殊类型在递归拷贝中的处理示例片段
function deepCloneWithSpecialTypes(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理日期对象
if (obj instanceof Date) {
return new Date(obj);
}
// 处理正则表达式
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理Map对象
if (obj instanceof Map) {
const newMap = new Map();
hash.set(obj, newMap);
obj.forEach((value, key) => {
newMap.set(deepCloneWithSpecialTypes(key, hash), deepCloneWithSpecialTypes(value, hash));
});
return newMap;
}
// 处理Set对象
if (obj instanceof Set) {
const newSet = new Set();
hash.set(obj, newSet);
obj.forEach(value => {
newSet.add(deepCloneWithSpecialTypes(value, hash));
});
return newSet;
}
// 其他类型(如ArrayBuffer, TypedArray等)可以继续添加
// ...
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
// 拷贝常规属性
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloneObj[key] = deepCloneWithSpecialTypes(obj[key], hash);
}
}
// 拷贝 Symbol 属性 (需要单独处理,因为 for...in 不会遍历它们)
Object.getOwnPropertySymbols(obj).forEach(symbol => {
cloneObj[symbol] = deepCloneWithSpecialTypes(obj[symbol], hash);
});
return cloneObj;
}选择哪种深拷贝方法,很大程度上取决于你的具体需求、数据结构的复杂性以及对浏览器兼容性的要求。没有一种“万能”的深拷贝方案能完美覆盖所有场景。
场景一:数据结构简单,不含函数、Date、RegExp、循环引用等特殊类型。
JSON.parse(JSON.stringify(obj))
场景二:需要处理大多数内置类型(Date, RegExp, Map, Set, TypedArray等),可能存在循环引用,且目标环境支持现代JS特性,但不涉及函数或DOM节点。
structuredClone()
postMessage
场景三:数据结构复杂,包含各种特殊类型(包括函数),存在循环引用,对兼容性有较高要求,或者需要高度定制化拷贝逻辑。
场景四:大型项目,追求开发效率和代码健壮性,不介意引入第三方依赖。
_.cloneDeep()
在实际开发中,我通常会先考虑
structuredClone()
JSON.parse(JSON.stringify())
以上就是JS如何实现深拷贝的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号