JSON.parse(JSON.stringify(obj))不是深拷贝,会丢弃undefined、function、Symbol、Date、RegExp、Map、Set等,且不支持循环引用;可靠方案包括手写递归(用WeakMap防循环)、Lodash的_.cloneDeep(精细类型处理)或原生structuredClone(现代环境首选,但不支持函数/undefined/Symbol)。

JavaScript 没有原生的“深拷贝”函数,JSON.parse(JSON.stringify(obj)) 看似简单,但会丢数据、报错、不支持函数/undefined/Symbol/Date/RegExp/Map/Set 等——它不是深拷贝,只是「JSON 安全子集的序列化还原」。
为什么 JSON.parse(JSON.stringify()) 不可靠
它在遇到以下情况时直接失效:
-
undefined、function、Symbol字段会被静默删除 - 循环引用(
obj.a = obj)抛出TypeError: Converting circular structure to JSON -
Date变成字符串("2023-01-01T00:00:00.000Z"),再 parse 回去是 string,不是 Date 实例 -
RegExp变成空对象{};Map/Set变成空对象或数组丢失键值关系 - 原型链、getter/setter、不可枚举属性全部丢失
手写递归深拷贝:支持常见类型 + 循环引用检测
适合需要可控、轻量、不依赖第三方的场景。核心是用 WeakMap 记录已拷贝对象,避免无限递归:
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj);
let clone;
if (obj instanceof Date) clone = new Date(obj);
else if (obj instanceof RegExp) clone = new RegExp(obj);
else if (obj instanceof Array) clone = [];
else clone = Object.create(Object.getPrototypeOf(obj));
map.set(obj, clone);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
注意:Object.create(Object.getPrototypeOf(obj)) 保留原型;hasOwnProperty 过滤原型属性;WeakMap 键只能是对象,天然适配引用追踪。
立即学习“Java免费学习笔记(深入)”;
Lodash 的 _.cloneDeep() 是什么原理?
它不是靠 JSON,也不是简单递归,而是对每种内置类型做精细识别和处理:
- 用
toString.call(value)判断类型(如[object Map]、[object Set]) - 对
Map先新建空 Map,再递归拷贝每个[key, value]对 - 对
ArrayBuffer/TypedArray使用.slice()或构造新实例 - 同样用内部缓存(类似
WeakMap)解决循环引用 - 支持自定义克隆逻辑(通过
customizer函数)
所以它比手写版更健壮,但也更重——如果项目已用 Lodash,直接调 _.cloneDeep(obj) 最省心;否则为一个工具函数引入整包不划算。
结构化克隆(structuredClone())能直接用吗?
这是浏览器/Node.js(v17.0+)原生提供的真正深拷贝 API,语义明确、性能好、支持多数现代类型:
- ✅ 支持
Date、RegExp、Map、Set、ArrayBuffer、TypedArray、BigInt、Error(部分)、嵌套对象/数组 - ✅ 自动处理循环引用
- ❌ 不支持
function、undefined、Symbol、Promise、Window等非可序列化值(会抛DataCloneError) - ⚠️ Node.js 需要显式启用 —— 启动时加
--enable-structured-cloning(v18.16+ 默认开启)
用法就是 structuredClone(obj),目前兼容性已覆盖 Chrome 98+、Firefox 94+、Safari 15.4+,生产环境可用,但得兜底:
function safeClone(obj) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(obj);
} catch (e) {
if (e.name === 'DataCloneError') console.warn('structuredClone failed:', e.message);
}
}
// fallback to lodash or custom impl
return deepClone(obj);
}
真正难的不是“怎么写”,而是想清楚你要拷什么:要不要函数?有没有循环引用?是否跨 iframe?是否需保留 getter?这些决定了该选 structuredClone、_.cloneDeep,还是自己控制粒度的手写方案。别让“看起来能跑”掩盖了数据静默丢失的风险。











