深拷贝递归复制整个对象树,包括嵌套对象、Date、RegExp等,确保原对象与拷贝完全独立;JSON.stringify() + parse仅适用于纯数据且丢弃undefined/function/Symbol等;手写需处理类型识别、特殊对象构造及循环引用;structuredClone()支持多类型但不兼容function/undefined/Symbol且有浏览器限制。

深拷贝到底在复制什么
深拷贝不是简单地复制变量值,而是递归地复制整个对象树——包括所有嵌套的对象、数组、Date、RegExp 等引用类型值。原始对象和拷贝对象完全独立,修改任一对象的任意层级属性,都不会影响另一个。
这和 Object.assign() 或展开运算符 {...obj} 的浅拷贝有本质区别:它们只复制第一层属性,内部嵌套的对象仍共享同一内存地址。
JSON.stringify() + JSON.parse() 为什么不能乱用
这是最常被误用的“深拷贝方案”,它只适用于纯数据对象(Plain Object)和数组,且有多个硬性限制:
-
undefined、function、Symbol字段会被直接丢弃 -
Date对象变成字符串(如"2024-05-12T08:30:00.000Z"),丢失原型和方法 -
RegExp变成空对象{} - 循环引用会直接抛出
TypeError: Converting circular structure to JSON - Map、Set、TypedArray、BigInt 等类型全部不支持
所以除非你 100% 确认输入是 JSON-safe 数据(比如后端返回的纯配置对象),否则不要把它当通用深拷贝手段。
立即学习“Java免费学习笔记(深入)”;
手写递归深拷贝要注意哪些关键点
一个健壮的手写实现必须处理类型识别、特殊对象构造、循环引用检测三类问题:
- 用
Object.prototype.toString.call()精确判断类型,而不是仅靠typeof或instanceof - 对
Date、RegExp、Array等需分别用new Date(obj)、new RegExp(obj)、Array.from(obj)构造新实例 - 必须用
WeakMap缓存已遍历的对象引用,遇到重复出现的同一对象时直接返回缓存结果,避免无限递归 - 普通对象使用
Object.create(Object.getPrototypeOf(obj))保持原型链,而不是简单{}
示例片段(简化版):
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
const type = Object.prototype.toString.call(obj);
let cloned;
if (type === '[object Array]') {
cloned = [];
} else if (type === '[object Date]') {
cloned = new Date(obj);
} else if (type === '[object RegExp]') {
cloned = new RegExp(obj);
} else {
cloned = Object.create(Object.getPrototypeOf(obj));
}
seen.set(obj, cloned);
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
cloned[key] = deepClone(obj[key], seen);
}
}
return cloned;
}
用 structuredClone() 前先看浏览器兼容性
structuredClone() 是目前最接近“开箱即用”的标准方案,支持 Map、Set、Date、RegExp、ArrayBuffer 等,也自动处理循环引用。
但它不是万能的:
- 不支持
function、undefined、Symbol—— 遇到会直接报错DataCloneError - Node.js 直到 v17.0+ 才默认启用,v16 需加
--enable-structured-cloning标志 - Chrome 98+、Firefox 94+、Safari 15.4+ 支持;IE 完全不支持
- 无法自定义克隆逻辑(比如过滤某些字段或转换值)
如果项目需要兼容老浏览器,或需定制行为(例如忽略某些 key、转换日期格式),还是得回退到手写或第三方库(如 lodash.cloneDeep)。
循环引用、原型链、不可枚举属性、访问器属性(getter/setter)——这些细节一旦漏掉一个,就不是真正的深拷贝。别让“看起来没报错”骗过自己。











