javascript递归深拷贝需用object.prototype.tostring.call精准识别类型,对date、regexp、map、set等内置类型单独重建,配合weakmap处理循环引用,才能保留原始类型。

JavaScript 中递归复制本身不能直接解决深拷贝的类型丢失问题,它只是实现深拷贝的一种手段;真正影响类型保留的是**如何识别和重建原始类型实例**。原生 JSON.parse(JSON.stringify()) 会丢掉函数、Date、RegExp、Map、Set、undefined、Symbol 等,而手动递归时若只处理 Object 和 Array,其他类型(如 Date)就会被降级为普通对象或字符串。
识别并保留内置构造器类型
关键是在递归前先判断值的精确类型,对特殊内置类型(Date、RegExp、Map、Set 等)单独处理,用对应构造器重建实例:
-
Date:用
new Date(obj.getTime())或new Date(obj.toString()) -
RegExp:用
new RegExp(obj.source, obj.flags) -
Map:新建
Map,遍历原Map的键值对,递归拷贝 key 和 value 后set() -
Set:新建
Set,遍历原Set元素,递归拷贝后add() -
ArrayBuffer、TypedArray:需用
slice()或new Constructor(buffer, ...)复制底层缓冲
避免简单 typeof 或 instanceof 误判
typeof 对多数对象都返回 "object",instanceof 在跨 iframe 或模块边界时可能失效。更可靠的方式是使用 Object.prototype.toString.call(value):
-
[object Date]→ 拷贝为新Date -
[object RegExp]→ 拷贝为新RegExp -
[object Map]、[object Set]→ 分别重建 -
[object Array]→ 递归处理数组元素 -
[object Object]→ 递归处理自有属性(注意过滤原型链)
处理循环引用与不可枚举属性
类型还原只是第一步,深拷贝还需防止爆栈和属性遗漏:
- 用
WeakMap缓存已拷贝的对象引用,遇到重复引用时直接返回缓存结果,避免无限递归 - 用
Object.getOwnPropertyDescriptors()获取包括get/set、enumerable、writable等完整描述符,再用Object.defineProperties()还原,而非仅遍历for...in - 对
undefined、Symbol、function,需明确策略:通常跳过、转为null,或浅拷贝(函数本身不可“深”拷贝,只能保留引用或字符串化)
一个最小可行示例(支持常见类型)
以下代码片段展示了如何在递归中保留 Date、RegExp、Map、Set 类型:
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
const tag = Object.prototype.toString.call(obj);
let clone;
switch (tag) {
case '[object Date]':
clone = new Date(obj.getTime());
break;
case '[object RegExp]':
clone = new RegExp(obj.source, obj.flags);
break;
case '[object Map]':
clone = new Map();
obj.forEach((v, k) => clone.set(deepClone(k, seen), deepClone(v, seen)));
break;
case '[object Set]':
clone = new Set();
obj.forEach(v => clone.add(deepClone(v, seen)));
break;
case '[object Array]':
clone = [];
for (let i = 0; i
<p>这个实现不依赖外部库,能准确重建多种内置类型,同时兼顾循环引用和属性特性。类型不丢失,靠的不是“递归本身”,而是对每种类型的显式识别与构造逻辑。</p>Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











