手写深拷贝 DeepClone:从原理到实践的完整指南

一、深拷贝与浅拷贝的本质区别

深拷贝与浅拷贝的核心差异在于对引用类型的处理方式。浅拷贝仅复制对象的第一层属性,若属性为引用类型(如对象、数组),则复制的是内存地址,新旧对象仍共享同一引用。而深拷贝会递归复制所有层级的属性,生成一个完全独立的新对象。

以简单对象为例:

  1. const original = { a: 1, b: { c: 2 } };
  2. const shallowCopy = { ...original }; // 浅拷贝
  3. const deepCopy = JSON.parse(JSON.stringify(original)); // 深拷贝(基础版)
  4. original.b.c = 3;
  5. console.log(shallowCopy.b.c); // 输出3(受影响)
  6. console.log(deepCopy.b.c); // 输出2(不受影响)

浅拷贝中,shallowCopy.boriginal.b 指向同一内存地址,修改会互相影响;而深拷贝通过完全复制,避免了这一问题。

二、为什么需要手写深拷贝?

尽管 JSON.parse(JSON.stringify()) 是常见的深拷贝方法,但它存在三大缺陷:

  1. 无法处理函数、Symbol、undefined:这些类型会被忽略或转为 null
  2. 无法处理循环引用:对象属性循环引用会导致栈溢出。
  3. 无法保留原型链:复制后的对象失去原型方法。

手写深拷贝的核心价值在于:

  • 完全控制复制逻辑:针对特定数据类型(如 Date、RegExp)定制处理。
  • 支持循环引用:通过弱引用表(WeakMap)避免无限递归。
  • 性能优化:跳过不可变类型(如数字、字符串)的递归。

三、手写深拷贝的实现步骤

1. 基础版本实现

  1. function deepClone(obj, hash = new WeakMap()) {
  2. // 处理基本类型和null/undefined
  3. if (obj === null || typeof obj !== 'object') {
  4. return obj;
  5. }
  6. // 处理循环引用
  7. if (hash.has(obj)) {
  8. return hash.get(obj);
  9. }
  10. // 处理Date、RegExp等特殊对象
  11. let cloneObj;
  12. if (obj instanceof Date) {
  13. cloneObj = new Date(obj);
  14. } else if (obj instanceof RegExp) {
  15. cloneObj = new RegExp(obj);
  16. } else {
  17. // 处理普通对象和数组
  18. cloneObj = Array.isArray(obj) ? [] : {};
  19. hash.set(obj, cloneObj); // 记录已复制对象
  20. // 递归复制属性
  21. for (let key in obj) {
  22. if (obj.hasOwnProperty(key)) {
  23. cloneObj[key] = deepClone(obj[key], hash);
  24. }
  25. }
  26. }
  27. return cloneObj;
  28. }

2. 关键点解析

(1)循环引用处理

通过 WeakMap 记录已复制的对象,当遇到已处理的对象时直接返回引用:

  1. const obj = { a: 1 };
  2. obj.self = obj; // 循环引用
  3. const cloned = deepClone(obj);
  4. console.log(cloned.self === cloned); // true

(2)特殊对象处理

  • Date:通过 new Date(obj) 复制时间戳。
  • RegExp:通过 new RegExp(obj) 复制正则表达式。
  • Map/Set:需遍历元素并递归复制。

(3)性能优化

  • 跳过不可变类型的递归(如 numberstring)。
  • 使用 for...in + hasOwnProperty 避免复制原型属性。

四、进阶场景处理

1. 复制函数

函数通常不需要深拷贝,但若需保留上下文,可通过 new Function() 重新生成:

  1. function cloneFunction(func) {
  2. const bodyReg = /(?<={)(.|\n)+(?=})/m;
  3. const paramReg = /(?<=\().+(?=\)\s+{)/;
  4. const funcString = func.toString();
  5. const param = paramReg.exec(funcString);
  6. const body = bodyReg.exec(funcString);
  7. return new Function(...(param ? param[0].split(',') : []), body ? body[0] : '');
  8. }

2. 复制DOM节点

DOM节点无法直接复制,但可通过 cloneNode 方法:

  1. function cloneDOM(node) {
  2. return node.cloneNode(true); // true表示深拷贝
  3. }

3. 复制Buffer(Node.js环境)

Buffer对象需通过 Buffer.from() 复制:

  1. function cloneBuffer(buf) {
  2. const copy = Buffer.alloc(buf.length);
  3. buf.copy(copy);
  4. return copy;
  5. }

五、测试与验证

1. 测试用例设计

  1. // 测试循环引用
  2. const obj = { a: 1 };
  3. obj.self = obj;
  4. const cloned = deepClone(obj);
  5. console.log(cloned.self === cloned); // true
  6. // 测试特殊对象
  7. const date = new Date();
  8. const clonedDate = deepClone(date);
  9. console.log(clonedDate instanceof Date && clonedDate.getTime() === date.getTime()); // true
  10. // 测试性能(对比JSON方法)
  11. const largeObj = { arr: new Array(10000).fill(1) };
  12. console.time('deepClone');
  13. deepClone(largeObj);
  14. console.timeEnd('deepClone'); // 约10ms
  15. console.time('JSON');
  16. JSON.parse(JSON.stringify(largeObj));
  17. console.timeEnd('JSON'); // 约5ms(但无法处理特殊对象)

2. 边界条件检查

  • 输入为 nullundefined
  • 对象包含 Symbol 属性。
  • 数组包含 undefinednull 元素。

六、实际应用建议

  1. 根据场景选择方法
    • 简单对象:JSON.parse(JSON.stringify())(快速但有限制)。
    • 复杂对象:手写深拷贝(全面控制)。
  2. 性能优化
    • 对大型对象,可跳过不可变属性的递归。
    • 使用 Object.create(null) 创建无原型对象提升速度。
  3. 库选择
    • 若不愿手写,可使用 lodash.cloneDeep 等成熟库。

七、总结

手写深拷贝的核心在于递归复制与循环引用处理。通过 WeakMap 解决循环问题,针对特殊对象(Date、RegExp)定制逻辑,最终实现一个健壮的深拷贝函数。实际开发中,需根据数据复杂度权衡性能与完整性,必要时结合工具库提升效率。

完整代码示例:

  1. function deepClone(obj, hash = new WeakMap()) {
  2. if (obj === null || typeof obj !== 'object') return obj;
  3. if (hash.has(obj)) return hash.get(obj);
  4. let cloneObj;
  5. if (obj instanceof Date) cloneObj = new Date(obj);
  6. else if (obj instanceof RegExp) cloneObj = new RegExp(obj);
  7. else if (Array.isArray(obj)) cloneObj = [];
  8. else if (obj instanceof Map) cloneObj = new Map();
  9. else if (obj instanceof Set) cloneObj = new Set();
  10. else cloneObj = Object.create(Object.getPrototypeOf(obj));
  11. hash.set(obj, cloneObj);
  12. if (obj instanceof Map) {
  13. obj.forEach((value, key) => {
  14. cloneObj.set(key, deepClone(value, hash));
  15. });
  16. } else if (obj instanceof Set) {
  17. obj.forEach(value => {
  18. cloneObj.add(deepClone(value, hash));
  19. });
  20. } else {
  21. for (let key in obj) {
  22. if (obj.hasOwnProperty(key)) {
  23. cloneObj[key] = deepClone(obj[key], hash);
  24. }
  25. }
  26. }
  27. return cloneObj;
  28. }

此实现覆盖了常见数据类型,并可通过扩展 if 分支支持更多场景。