递归函数是javascript中实现多层嵌套对象深度拷贝最基础可控的方式,需依次处理null/非对象、数组、date、regexp、map、set等类型,并用weakmap防循环引用。

JavaScript 中实现多层嵌套对象的深度拷贝,递归函数是最基础、最可控的方式之一。核心思路是:对每个属性值判断类型,若为对象(包括数组、普通对象、Date、RegExp 等),就递归处理;否则直接赋值。
基础递归实现(支持普通对象和数组)
这是最简明的递归深拷贝,适用于大多数纯数据结构:
- 先判断是否为 null 或非对象类型(如 string、number、boolean、undefined),直接返回原值
- 再判断是否为数组,用
[]初始化并遍历递归拷贝每一项 - 其余情况当作普通对象,用
{}初始化,遍历Object.keys()递归拷贝每个属性值
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
增强版:支持 Date、RegExp、Map、Set 等内置类型
原生对象如 Date、RegExp、Map、Set 不能靠简单遍历还原,需单独识别并构造新实例:
-
obj instanceof Date→ 返回new Date(obj) -
obj instanceof RegExp→ 返回new RegExp(obj) -
obj instanceof Map→ 创建新 Map,遍历obj.entries()递归赋值 -
obj instanceof Set→ 创建新 Set,遍历obj.values()递归添加
注意:这些类型要放在对象/数组判断之前,避免被误判为普通对象。
防循环引用(关键健壮性优化)
如果对象存在自引用或循环引用(例如 obj.a = obj),基础递归会无限调用导致栈溢出。解决方法是用 WeakMap 缓存已拷贝过的源对象及其对应克隆体:
- 每次进入函数,先查 WeakMap 是否已有该对象的克隆结果,有则直接返回
- 无则新建克隆体,并立即存入 WeakMap(在递归前),避免后续重复处理
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
let cloned;
if (obj instanceof Date) cloned = new Date(obj);
else if (obj instanceof RegExp) cloned = new RegExp(obj);
else if (obj instanceof Map) {
cloned = new Map();
seen.set(obj, cloned);
obj.forEach((value, key) => cloned.set(key, deepClone(value, seen)));
} else if (obj instanceof Set) {
cloned = new Set();
seen.set(obj, cloned);
obj.forEach(value => cloned.add(deepClone(value, seen)));
} else if (Array.isArray(obj)) {
cloned = [];
seen.set(obj, cloned);
obj.forEach((item, i) => cloned[i] = deepClone(item, seen));
} else {
cloned = {};
seen.set(obj, cloned);
Object.keys(obj).forEach(key => {
cloned[key] = deepClone(obj[key], seen);
});
}
return cloned;
}
注意事项与边界说明
递归深拷贝无法复制以下内容,这是 JavaScript 语言限制,不是实现缺陷:
- 函数(
function)、Symbol 键、原型链(__proto__)、不可枚举属性(除非显式用Object.getOwnPropertyDescriptors) - DOM 节点、Window、document 等宿主对象(它们通常不可序列化)
- 某些特殊内部属性(如
Promise、TypedArray的 buffer 引用关系需更精细处理)
如需完整覆盖,建议使用成熟库如 lodash.cloneDeep 或 structuredClone(现代环境支持,但暂不支持函数和循环引用)。自己写递归适合学习原理、定制逻辑或轻量场景。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











