为什么需要单独实现深拷贝函数?
先看两个现象
const obj1 = { a: { b: { c: 1, d: 2 } } }
const obj2 = obj1
obj2.a = 1
console.log(obj1)
// 输出{ a: 1 }
const obj1 = { a: { b: { c: 1, d: 2 } } }
const obj3 = { ...obj1 }
obj3.a.b = 1
console.log(obj1)
// 输出{ a: { b: 1 } }
我们来详细看一下第二段代码
内存布局如下:
(栈内存 存放基本数据类型(number、string、boolean、null、undefined、symbol、bigint)和引用类型的指针(内存地址))
(堆内存 存放引用类型的实际数据(如 Object、Array、Function等))
[栈内存]
obj1 → 0x100 (堆内存地址)
obj3 → 0x200 (堆内存地址)
[堆内存]
0x100: { a → 0x300 }
0x200: { a → 0x300 } // 浅拷贝复制了引用
0x300: { b → 0x400 }
0x400: { c: 1, d: 2 }
图解

修改操作的影响
内存变化过程:
- 通过
obj3.a找到0x300 - 修改
0x300.b的值从{ c: 1, d: 2 }到 1
修改后堆内存
[修改后堆内存]
0x300: 1 // 原始值被修改
最终内存状态
[栈内存]
obj1 → 0x100
obj3 → 0x200
[堆内存]
0x100: { a → 0x300 }
0x200: { a → 0x300 }
0x300: { b: 1 }
1.引用类型的特点
JavaScript 中的对象是引用类型,赋值操作只是复制引用:
const original = { a: 1, b: { c: 2 } };
const copy = original; // 只是引用复制
copy.a = 3;
console.log(original.a); // 3 (原对象也被修改)
2.内置方法无法实现深拷贝
Object.assign()和扩展运算符...只能浅拷贝JSON.parse(JSON.stringify())有局限性:
const obj = {
date: new Date(),
func: () => console.log('hi'),
undef: undefined
};
const jsonCopy = JSON.parse(JSON.stringify(obj));
// { date: "2023-05-01T12:00:00.000Z" }
// 函数和undefined丢失,Date对象转为字符串
3.特殊对象类型的处理
需要单独处理以下类型:
Date对象RegExp对象Map/Set集合- 函数 (通常选择不拷贝)
- 循环引用 (对象引用自身)
4.性能与安全考虑
- 深拷贝是递归操作,需要性能优化
- 防止拷贝过程中原型链污染
- 避免意外修改共享的引用数据
怎么实现? 先别想太多
先别想太多 思路很简单 先设置个新的空对象用于拷贝 再遍历原对象的每个key 如果key对应的value 是基础类型那就直接赋值给空对象即可 如果是对象类型那就继续遍历这个对象 —> 这里很明显要递归调用了
代码如下

输出:
{ a: { b: { c: 1, d: 2 } } }
{ a: 1 }
说明已经成功完成深拷贝
想多一点! 代码不太严谨
潜在以下问题
1.无法处理数组
如果 obj是数组,你的代码会错误地把它转成普通对象:
const arr = [1, 2, { a: 3 }];
const clonedArr = deepClone(arr);
console.log(clonedArr); // 输出:{ 0: 1, 1: 2, 2: { a: 3 } }(不是数组!)
修复方法:
const result = Array.isArray(obj) ? [] : {};
2.无法处理循环对象
如果对象有循环引用(如 obj.self = obj),会进入无限递归导致栈溢出:
如上图 第10行代码会无限递归执行
const obj = { a: 1 };
obj.self = obj; // 循环引用
deepClone(obj); // 报错:Maximum call stack size exceeded
修复方法:使用 WeakMap缓存已拷贝的对象:
简单介绍一下weakMap:
WeakMap 是 JavaScript 中的一种特殊键值对集合,与普通 Map类似,但有以下关键区别:
键必须是对象(不能是基本类型:number、string等)
const deepClone = (obj, cache = new WeakMap()) => {
if (cache.has(obj)) return cache.get(obj); // 如果已拷贝过,直接返回
const result = Array.isArray(obj) ? [] : {};
cache.set(obj, result); // 缓存当前对象
// ...其余逻辑不变
};
3.无法处理特殊对象(如RegExp, Date, Set, Map)
如果对象包含 Date、RegExp等特殊对象,会被直接当作普通对象拷贝:
const obj = { date: new Date(), regex: /abc/g };
const cloned = deepClone(obj);
console.log(cloned.date); // 输出:普通对象 { ... },而不是 Date 实例
修复方法:特殊处理这些类型
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
改进后的最佳实践
function deepClone(obj, cache = new WeakMap()) {
// 基本类型直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (cache.has(obj)) {
return cache.get(obj);
}
// 处理 Date 对象
// instanceof:JavaScript 运算符,用于检查左侧对象是否是右侧构造函数的实例
if (obj instanceof Date) {
return new Date(obj);
}
// 处理 RegExp 对象
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 初始化克隆对象(数组 or 普通对象)
const clone = Array.isArray(obj) ? [] : {};
// 缓存当前对象,防止循环引用
cache.set(obj, clone);
// 克隆普通属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], cache);
}
}
return clone;
}
测试用例
const obj = {
a: 1,
b: { c: 2 },
d: new Date(),
e: /regex/g,
f: [1, 2, { g: 3 }]
};
obj.self = obj; // 循环引用
const cloned = deepClone(obj);
console.log(cloned);
输出
{
a: 1,
b: { c: 2 },
d: 2025-09-27T00:00:00.000Z, // Date 类型保留
e: /regex/g, // RegExp 类型保留
f: [1, 2, { g: 3 }], // 数组和嵌套对象正确克隆
self: [Circular] // 循环引用被正确处理
}