一、深拷贝与浅拷贝的本质区别
深拷贝与浅拷贝的核心差异在于对引用类型的处理方式。浅拷贝仅复制对象的第一层属性,若属性为引用类型(如对象、数组),则复制的是内存地址,新旧对象仍共享同一引用。而深拷贝会递归复制所有层级的属性,生成一个完全独立的新对象。
以简单对象为例:
const original = { a: 1, b: { c: 2 } };const shallowCopy = { ...original }; // 浅拷贝const deepCopy = JSON.parse(JSON.stringify(original)); // 深拷贝(基础版)original.b.c = 3;console.log(shallowCopy.b.c); // 输出3(受影响)console.log(deepCopy.b.c); // 输出2(不受影响)
浅拷贝中,shallowCopy.b 与 original.b 指向同一内存地址,修改会互相影响;而深拷贝通过完全复制,避免了这一问题。
二、为什么需要手写深拷贝?
尽管 JSON.parse(JSON.stringify()) 是常见的深拷贝方法,但它存在三大缺陷:
- 无法处理函数、Symbol、undefined:这些类型会被忽略或转为
null。 - 无法处理循环引用:对象属性循环引用会导致栈溢出。
- 无法保留原型链:复制后的对象失去原型方法。
手写深拷贝的核心价值在于:
- 完全控制复制逻辑:针对特定数据类型(如 Date、RegExp)定制处理。
- 支持循环引用:通过弱引用表(WeakMap)避免无限递归。
- 性能优化:跳过不可变类型(如数字、字符串)的递归。
三、手写深拷贝的实现步骤
1. 基础版本实现
function deepClone(obj, hash = new WeakMap()) {// 处理基本类型和null/undefinedif (obj === null || typeof obj !== 'object') {return obj;}// 处理循环引用if (hash.has(obj)) {return hash.get(obj);}// 处理Date、RegExp等特殊对象let cloneObj;if (obj instanceof Date) {cloneObj = new Date(obj);} else if (obj instanceof RegExp) {cloneObj = new RegExp(obj);} else {// 处理普通对象和数组cloneObj = Array.isArray(obj) ? [] : {};hash.set(obj, cloneObj); // 记录已复制对象// 递归复制属性for (let key in obj) {if (obj.hasOwnProperty(key)) {cloneObj[key] = deepClone(obj[key], hash);}}}return cloneObj;}
2. 关键点解析
(1)循环引用处理
通过 WeakMap 记录已复制的对象,当遇到已处理的对象时直接返回引用:
const obj = { a: 1 };obj.self = obj; // 循环引用const cloned = deepClone(obj);console.log(cloned.self === cloned); // true
(2)特殊对象处理
- Date:通过
new Date(obj)复制时间戳。 - RegExp:通过
new RegExp(obj)复制正则表达式。 - Map/Set:需遍历元素并递归复制。
(3)性能优化
- 跳过不可变类型的递归(如
number、string)。 - 使用
for...in+hasOwnProperty避免复制原型属性。
四、进阶场景处理
1. 复制函数
函数通常不需要深拷贝,但若需保留上下文,可通过 new Function() 重新生成:
function cloneFunction(func) {const bodyReg = /(?<={)(.|\n)+(?=})/m;const paramReg = /(?<=\().+(?=\)\s+{)/;const funcString = func.toString();const param = paramReg.exec(funcString);const body = bodyReg.exec(funcString);return new Function(...(param ? param[0].split(',') : []), body ? body[0] : '');}
2. 复制DOM节点
DOM节点无法直接复制,但可通过 cloneNode 方法:
function cloneDOM(node) {return node.cloneNode(true); // true表示深拷贝}
3. 复制Buffer(Node.js环境)
Buffer对象需通过 Buffer.from() 复制:
function cloneBuffer(buf) {const copy = Buffer.alloc(buf.length);buf.copy(copy);return copy;}
五、测试与验证
1. 测试用例设计
// 测试循环引用const obj = { a: 1 };obj.self = obj;const cloned = deepClone(obj);console.log(cloned.self === cloned); // true// 测试特殊对象const date = new Date();const clonedDate = deepClone(date);console.log(clonedDate instanceof Date && clonedDate.getTime() === date.getTime()); // true// 测试性能(对比JSON方法)const largeObj = { arr: new Array(10000).fill(1) };console.time('deepClone');deepClone(largeObj);console.timeEnd('deepClone'); // 约10msconsole.time('JSON');JSON.parse(JSON.stringify(largeObj));console.timeEnd('JSON'); // 约5ms(但无法处理特殊对象)
2. 边界条件检查
- 输入为
null或undefined。 - 对象包含
Symbol属性。 - 数组包含
undefined或null元素。
六、实际应用建议
- 根据场景选择方法:
- 简单对象:
JSON.parse(JSON.stringify())(快速但有限制)。 - 复杂对象:手写深拷贝(全面控制)。
- 简单对象:
- 性能优化:
- 对大型对象,可跳过不可变属性的递归。
- 使用
Object.create(null)创建无原型对象提升速度。
- 库选择:
- 若不愿手写,可使用
lodash.cloneDeep等成熟库。
- 若不愿手写,可使用
七、总结
手写深拷贝的核心在于递归复制与循环引用处理。通过 WeakMap 解决循环问题,针对特殊对象(Date、RegExp)定制逻辑,最终实现一个健壮的深拷贝函数。实际开发中,需根据数据复杂度权衡性能与完整性,必要时结合工具库提升效率。
完整代码示例:
function deepClone(obj, hash = new WeakMap()) {if (obj === null || typeof obj !== 'object') return obj;if (hash.has(obj)) return hash.get(obj);let cloneObj;if (obj instanceof Date) cloneObj = new Date(obj);else if (obj instanceof RegExp) cloneObj = new RegExp(obj);else if (Array.isArray(obj)) cloneObj = [];else if (obj instanceof Map) cloneObj = new Map();else if (obj instanceof Set) cloneObj = new Set();else cloneObj = Object.create(Object.getPrototypeOf(obj));hash.set(obj, cloneObj);if (obj instanceof Map) {obj.forEach((value, key) => {cloneObj.set(key, deepClone(value, hash));});} else if (obj instanceof Set) {obj.forEach(value => {cloneObj.add(deepClone(value, hash));});} else {for (let key in obj) {if (obj.hasOwnProperty(key)) {cloneObj[key] = deepClone(obj[key], hash);}}}return cloneObj;}
此实现覆盖了常见数据类型,并可通过扩展 if 分支支持更多场景。