如何实现一个深拷贝函数?
描述了在 JavaScript 中使用 JSON 序列化和反序列化以及自定义递归深拷贝函数来实现对象的深层复制。讨论了各自的优缺点、适用场景及限制,还涉及了边界问题处理和优化建议。
在JavaScript中实现深拷贝的目的是创建一个新对象,新对象及其所有属性值从源对象完整独立复制、不共享内存、互不影响修改。基本数据直接复制,引用类型(对象、数组等)则递归到基本值复制或创建新实例。主要方法如下:
1. 使用JSON序列化和反序列化(简单但局限性大)
这是一种便捷方式,但仅适用于JSON安全(即无函数、undefined、循环引用等),适用于非复杂数据:
function deepCopyJSON(obj) {
return JSON.parse(JSON.stringify(obj));
}
// 示例
const obj = { name: 'foo', data: { a: 1 } };
const copy = deepCopyJSON(obj);
copy.data.a = 2;
console.log(obj.data.a); // 输出1,不影响原对象
- 限制:
- 跳过值为
function
、undefined
、Symbol的键 - 不能正确处理Date(格式转换损失)、RegExp或类实例
- 不支持循环引用(如obj.prop = obj),会报错[]
- 跳过值为
2. 自定义递归深拷贝函数(更完整推荐)
递归遍历所有属性层级:基本值直接复制,复杂类型使用递归处理数组或对象。
function deepClone(src) {
// 如果类型为原始值或null,直接返回(深拷贝只关心引用类型)
if (src === null || typeof src !== 'object') {
return src;
}
let target;
// 处理数组
if (Array.isArray(src)) {
target = [];
for (let i = 0; i < src.length; i++) {
target[i] = deepClone(src[i]);
}
return target;
}
// 处理普通对象(确保不复制原型链上的属性)
target = {};
for (let key in src) {
if (src.hasOwnProperty(key)) {
target[key] = deepClone(src[key]); // 递归深层复制
}
}
return target;
}
// 示例:无副作用的完整复制
const orig = { a: 1, b: };
const copy = deepClone(orig);
copy.b.push(3);
console.log(orig.b); // 输出, 原对象未修改
- 关键优点:
- 处理基本数字、字符串、对象、嵌套数组
- 使用
hasOwnProperty
跳过继承属性,保证独立性 - 直接复现常见引用类型,支持原型逻辑
3. 处理边界问题和优化
此方法可扩展到更广泛情形:
- 特殊类型:
- Date:替换为
target = new Date(src.getTime());
- Map/Set:遍历元素用新实例存储
- Circular references(循环引用):添加“内存备忘录字典”如
map = new Map()
储存已有对象ID
- Date:替换为
- 推荐实践:
- 初始递归版用于一般JS面试
- 实际工程可用库如 Lodash 的
_.cloneDeep()
兼容多平台
- 面试提醒:递归深度或复杂引用如超大对象可能导致栈溢出,可用栈数据结构改写迭代版