递归深拷贝需精准判断类型、完整遍历属性、手动构建新结构,支持嵌套数组、对象、date、regexp及symbol键,避免引用共享与数据丢失。

JavaScript 中用递归克隆包含嵌套数组的对象,核心是**区分数据类型、逐层遍历、手动构造新结构**,避免引用共享。浅拷贝(如 Object.assign 或展开运算符)无法处理嵌套对象或数组,而 JSON.parse(JSON.stringify(obj)) 会丢弃函数、undefined、Date、RegExp、循环引用等,也不适用于 Symbol 键。递归深拷贝能精准控制逻辑,兼容各种边界情况。
判断类型并分路径处理
不能只用 typeof,因为数组和对象都返回 "object"。应优先用 Array.isArray() 判断数组,再用 Object.prototype.toString.call() 或 obj !== null && typeof obj === 'object' 判断普通对象。基本类型(string、number、boolean、null、undefined、symbol、bigint)直接返回,无需递归。
-
数组:创建新空数组,遍历原数组每一项,递归拷贝后
push进去 -
普通对象:创建新空对象,遍历
Object.keys()或Object.getOwnPropertyNames()(保留不可枚举属性),对每个键的值递归拷贝并赋值 -
Date / RegExp 等内置对象:可选择性处理,例如
new Date(obj)或new RegExp(obj)
正确遍历对象属性(含 Symbol 和不可枚举属性)
仅用 for...in 会遍历原型链;仅用 Object.keys() 会忽略不可枚举属性和 Symbol 键。若需完整克隆,推荐组合使用:
-
Object.getOwnPropertyNames(obj):获取所有自身可枚举 + 不可枚举字符串键 -
Object.getOwnPropertySymbols(obj):获取所有自身 Symbol 键 - 对两类键分别遍历,用
Object.getOwnPropertyDescriptor()可保留writable、enumerable、configurable等属性描述符(进阶需求)
简单场景下,用 Reflect.ownKeys(obj) 一行获取全部自身键(含字符串和 Symbol)更简洁。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
处理循环引用(可选但重要)
如果对象存在自引用或深层循环引用(如 a.b = a),不加检测的递归会爆栈。解决方法是维护一个 WeakMap 缓存已处理过的源对象及其对应的新对象:
- 每次进入递归前,先查
cache.has(obj),命中则直接返回缓存值 - 未命中则新建目标对象(数组或 {}),立即
cache.set(obj, target),再递归填充内容 -
WeakMap适合此场景——键必须是对象,且不阻止垃圾回收,安全高效
一个轻量实用的递归深拷贝函数示例
以下代码覆盖常见需求(嵌套数组、普通对象、null/undefined、Date、RegExp、Symbol 键),不含循环引用处理;如需支持循环引用,按上一节加入 cache 即可:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
const cloned = {};
const keys = Reflect.ownKeys(obj);
for (const key of keys) {
cloned[key] = deepClone(obj[key]);
}
return cloned;
}
使用示例:
const original = {
name: 'Alice',
hobbies: ['coding', { tool: 'VS Code' }],
meta: { created: new Date('2023-01-01') },
[Symbol('id')]: 123
};
const copy = deepClone(original);
copy.hobbies[1].tool = 'WebStorm'; // 不影响 original
console.log(original.hobbies[1].tool); // 'VS Code'
不复杂但容易忽略细节。关键是类型判断要准、遍历要全、递归调用要稳。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










