手写基础版深拷贝需递归遍历对象/数组,用object.prototype.tostring.call精准判型,仅对plain object和array递归,null单独处理,date/regexp新建实例,object.keys遍历自有可枚举属性,数组用[]、对象用{}初始化并挂载递归返回值。

手写基础版深拷贝,核心在于用递归遍历对象/数组的每一层属性,并对每个值做类型判断与独立复制,避免引用共享。关键不是堆砌功能,而是厘清属性遍历路径和递归调用栈的进出逻辑。
识别可继续递归的类型并终止边界
只对 plain object 和 array 递归,其他类型(null、基本类型、Date、RegExp、Map、Set 等)直接返回副本或原始值。特别注意:null 是 object 类型但不能递归,必须单独判断。
- 用
Object.prototype.toString.call(val)精准判断类型,比typeof更可靠 - 遇到 null、undefined、字符串、数字、布尔、Symbol、BigInt 直接返回(它们不可变,无需深拷)
- 遇到 Date、RegExp 可新建实例:
new Date(obj)、new RegExp(obj)
遍历自有可枚举属性,跳过原型链与不可枚举键
只处理对象自身的可枚举属性,用 Object.keys() 或 for...in + hasOwnProperty,不遍历原型上属性,也不处理 Symbol 键(基础版暂不支持)。
-
Object.keys(obj)返回字符串键数组,天然过滤 Symbol 和不可枚举属性 - 对数组用
obj.length创建新数组,再逐项赋值;对对象用{}初始化,再循环赋值 - 每轮递归前检查是否已拷贝过当前引用(防循环引用),基础版可先省略,但要意识到这是递归栈“深度”与“重复访问”的分界点
递归调用与返回值组装同步进行
每次递归调用都应返回一个**新创建的结构体**({} 或 []),父层拿到后立即挂载到对应 key 或 index 上,整个过程是自底向上构建新树。
- 例如拷贝
{a: {b: 1}}:先递归进{b: 1},返回新对象{b: 1};再在上层把该对象赋给result.a - 不要在递归内修改外部变量,所有中间结果都靠 return 传递,保持函数纯度
- 栈帧退出时,当前层级的 result 已完整,交给上一层使用 —— 这就是递归栈“调用→计算→返回→组装”的基本节奏
基础版代码骨架(无循环引用处理)
下面是一个聚焦逻辑主干、注释直指关键点的实现:
function deepClone(obj) {
// 边界:null 和非对象直接返回
if (obj === null || typeof obj !== 'object') return obj;
// 区分数组和普通对象
const result = Array.isArray(obj) ? [] : {};
// 遍历自有可枚举属性
for (const key of Object.keys(obj)) {
// 递归拷贝每个值,并挂载到 result 对应位置
result[key] = deepClone(obj[key]);
}
return result;
}
这段代码跑通了属性遍历、类型分发、递归调用、结果组装四个环节,栈帧清晰可见。后续扩展(如支持 Map/Set、处理循环引用、保留不可枚举属性)都是在此骨架上叠加判断与状态管理。











