JavaScript无内置深拷贝函数;JSON序列化会丢失函数、undefined等;structuredClone()是目前最接近标准的原生方案,支持Date、Map、Set等但不支持function、Symbol及自定义类实例。
JavaScript 中没有内置的“深拷贝”函数,JSON.parse(JSON.stringify(obj)) 看似简单,但会丢函数、undefined、Symbol、循环引用、Date、RegExp 等,不能算真正可靠的深拷贝。
浅拷贝(如 Object.assign()、展开运算符 {...obj}、Array.prototype.slice())只复制对象/数组的顶层属性,嵌套对象仍共享内存地址。修改嵌套值会影响原对象。
深拷贝则递归遍历每个属性,为每个对象或数组新建内存空间,彻底隔离修改影响。
常见误判场景:
structuredClone() 但没检查浏览器兼容性(Chrome 98+、Firefox 94+ 支持,Safari 15.4+ 部分支持)lodash.cloneDeep() 能处理所有类型,其实对某些自定义类实例或带不可枚举属性的对象仍有局限require('util').inspect() 或 vm.createContext() 模拟深拷贝——完全不推荐,语义错误且极不稳定这是 ECMAScript 提案已落地的原生 API,能正确处理 Date、Map、Set、RegExp、ArrayBuffer、TypedArray、Error(部分)、以及嵌套结构和循环引用(自动跳过或报错,取决于环境)。
但它不支持:
function、undefined、Symbol
prototype 的自定义类实例(只保留数据,丢失方法)window、document 等宿主对象const original = {
a: 1,
b: { c: 2 },
d: new Date(),
e: /test/g,
f: new Set([1, 2])
};
const copy = structuredClone(original);
copy.b.c = 99;
console.log(original.b.c); // 2 —— 不受影响
手写时最常被忽略的是循环引用(对象属性指向自身或形成环),不处理会导致栈溢出。必须用 WeakMap 缓存已拷贝过的对象引用。
基础逻辑要点:
typeof + Array.isArray() + Object.prototype.toString.call() 组合判断类型null、基本类型(string/number/boolean/bigint/symbol)直接返回Date、RegExp 等特殊对象调用构造函数重建(new Date(obj))WeakMap 记录映射关系防死循环function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj);
const cloned = Array.isArray(obj) ? [] : {};
map.set(obj, cloned);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key], map);
}
}
return cloned;
}
不要为了“看起来通用”而强行统一所有场景的深拷贝逻辑。比如后端传来的纯 JSON 数据,JSON.parse(JSON.stringify(x)) 完全够用;但涉及用户上传的富文本状态(含 Map、Set)就必须用 structuredClone()。
最容易被忽略的一点:深拷贝不是免费的。嵌套越深、数据越大,性能开销越明显。如果只是临时读取配置,考虑是否真的需要深拷贝——有时 Object.freeze() + 不可变更新更轻量
