浅拷贝与深拷贝
1. 概念与区别
(1) 浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
- 如果属性是基本类型,拷贝的就是基本类型的值。
- 如果属性是引用类型,拷贝的就是内存地址 (引用)。如果其中一个对象改变了这个地址,就会影响到另一个对象。
实现方式:
Object.assign
- 展开运算符
...
Array.prototype.slice() / concat() (针对数组)
(2) 深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
实现方式:
JSON.parse(JSON.stringify()) (最简单,有缺陷)
structuredClone (现代浏览器 API)
- 手写递归 (最稳妥)
- 第三方库
lodash.cloneDeep
2. 浅拷贝示例 (面试手写概率较低)
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);
obj1.a = 3; // obj2.a 不变 (1)
obj1.b.c = 4; // obj2.b.c 变为 4 (引用共享)
3. 深拷贝实现方案 (面试重点)
(1) JSON 序列化
const obj2 = JSON.parse(JSON.stringify(obj1));
缺陷 (高频考点):
- 忽略 undefined 属性。
- 忽略 Symbol 属性。
- 忽略 function (会将函数变为 undefined 或者抛错)。
- Date 对象变为字符串。
- RegExp 对象变为
{}。
- 循环引用 会报错 (
Converting circular structure to JSON)。
(2) structuredClone (ES2022)
const obj2 = structuredClone(obj1);
优点:解决了 JSON 方案的循环引用和大部分特殊类型丢失问题 (支持 Date, RegExp, Map, Set, ArrayBuffer 等)。
缺陷:
- 不支持 function (无法克隆函数)。
- 不支持 DOM 节点。
- 如果不兼容旧环境,需 polyfill。
(3) 手写递归 DeepClone
能够处理循环引用是一个关键加分项 (使用 WeakMap)。
function deepClone(obj, hash = new WeakMap()) {
// 1. 处理 null 和 基本类型
if (obj === null || typeof obj !== 'object') return obj;
// 2. 处理 Date 和 RegExp
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 3. 处理循环引用 (如果之前拷贝过,直接返回引用)
if (hash.has(obj)) return hash.get(obj);
// 4. 创建新对象 (保持原型链,兼容 Array 和 Object)
// 或者 const cloneObj = Array.isArray(obj) ? [] : {};
const cloneObj = new obj.constructor();
// 5. 将当前对象存入 hash,防止循环引用死循环
hash.set(obj, cloneObj);
// 6. 递归拷贝属性 (使用 Reflect.ownKeys 可遍历 Symbol)
// for...in 只遍历可枚举属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
4. 面试总结
- 浅拷贝:一层复制,引用共享。(
Object.assign, ...)
- 深拷贝:完全独立。(
JSON, structuredClone, 递归)
- JSON 缺陷:忽略
undefined/symbol/function,不支持循环引用。
- 手写要点:递归 + 类型判断 +
WeakMap 防循环引用。