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())只复制对象/数组的顶层属性,嵌套对象仍共享内存地址。修改嵌套值会影响原对象。
深拷贝则递归遍历每个属性,为每个对象或数组新建内存空间,彻底隔离修改影响。
常见误判场景:
立即学习“Java免费学习笔记(深入)”;
- 用
structuredClone()但没检查浏览器兼容性(Chrome 98+、Firefox 94+ 支持,Safari 15.4+ 部分支持) - 以为
lodash.cloneDeep()能处理所有类型,其实对某些自定义类实例或带不可枚举属性的对象仍有局限 - 在 Node.js 中直接用
require('util').inspect()或vm.createContext()模拟深拷贝——完全不推荐,语义错误且极不稳定
structuredClone() 是目前最接近标准的深拷贝方案
这是 ECMAScript 提案已落地的原生 API,能正确处理 Date、Map、Set、RegExp、ArrayBuffer、TypedArray、Error(部分)、以及嵌套结构和循环引用(自动跳过或报错,取决于环境)。
但它不支持:
-
function、undefined、Symbol - 带有
prototype的自定义类实例(只保留数据,丢失方法) - DOM 节点、
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;
}
生产环境优先选 structuredClone(),降级用 lodash 或自研方案要明确边界
不要为了“看起来通用”而强行统一所有场景的深拷贝逻辑。比如后端传来的纯 JSON 数据,JSON.parse(JSON.stringify(x)) 完全够用;但涉及用户上传的富文本状态(含 Map、Set)就必须用 structuredClone()。
最容易被忽略的一点:深拷贝不是免费的。嵌套越深、数据越大,性能开销越明显。如果只是临时读取配置,考虑是否真的需要深拷贝——有时 Object.freeze() + 不可变更新更轻量、更可控。










