JavaScript 深拷贝:从基础到完美实现

深拷贝是 JavaScript 开发中常见的需求,本文将带你从基础概念开始,逐步深入,最终实现一个完美的深拷贝函数。

一、浅拷贝 vs 深拷贝

1. 浅拷贝

浅拷贝只复制对象的第一层属性,更深层次的属性仍然是共享的。

const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };

shallowCopy.b.c = 3;
console.log(obj.b.c); // 3 - 原对象也被修改了

2. 深拷贝

深拷贝会递归复制对象的所有层级,创建完全独立的副本。

const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));

deepCopy.b.c = 3;
console.log(obj.b.c); // 2 - 原对象不受影响

局限性:

  • 不能处理函数、undefined、Symbol
  • 不能处理循环引用
  • 会丢失 Date 对象(转为字符串)
  • 会丢失 RegExp 对象
  • 会丢失原型链

2. 递归实现(基础版)

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  let clone = Array.isArray(obj) ? [] : {};
  
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key]);
    }
  }
  
  return clone;
}

问题:无法处理循环引用

三、处理循环引用

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  let clone = Array.isArray(obj) ? [] : {};
  hash.set(obj, clone);
  
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], hash);
    }
  }
  
  return clone;
}

四、处理特殊对象

1. 处理 Date 和 RegExp

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理特殊对象
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  
  if (hash.has(obj)) return hash.get(obj);
  //自定义对象处理
  let clone = new obj.constructor();
  hash.set(obj, clone);
  
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], hash);
    }
  }
  
  return clone;
}

2. 处理 Map 和 Set

function deepClone(obj, hash = new WeakMap()) {
  // ...前面的代码...
  
  if (obj instanceof Map) {
    const clone = new Map();
    hash.set(obj, clone);
    obj.forEach((value, key) => {
      clone.set(deepClone(key, hash), deepClone(value, hash));
    });
    return clone;
  }
  
  if (obj instanceof Set) {
    const clone = new Set();
    hash.set(obj, clone);
    obj.forEach(value => {
      clone.add(deepClone(value, hash));
    });
    return clone;
  }
  
  // ...后面的代码...
}

五、处理 Symbol 属性和原型链

function deepClone(obj, hash = new WeakMap()) {
  // 处理原始值和null
  if (Object(obj) !== obj || obj === null) return obj;
  
  // 处理特殊对象
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (obj instanceof Map) {
    const clone = new Map();
    hash.set(obj, clone);
    obj.forEach((value, key) => {
      clone.set(deepClone(key, hash), deepClone(value, hash));
    });
    return clone;
  }
  if (obj instanceof Set) {
    const clone = new Set();
    hash.set(obj, clone);
    obj.forEach(value => {
      clone.add(deepClone(value, hash));
    });
    return clone;
  }
  
  // 处理循环引用
  if (hash.has(obj)) return hash.get(obj);
  
  // 获取所有属性,包括Symbol
  const allKeys = Reflect.ownKeys(obj);
  // 获取原型
  const proto = Object.getPrototypeOf(obj);
  // 创建新对象,保持原型链
  const clone = Object.create(proto);
  hash.set(obj, clone);
  
  // 复制所有属性
  allKeys.forEach(key => {
    clone[key] = deepClone(obj[key], hash);
  });
  
  return clone;
}

六、最终完美实现

function deepClone(obj, hash = new WeakMap()) {
  // 处理原始值和null
  if (Object(obj) !== obj || obj === null) return obj;
  
  // 处理特殊对象
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (obj instanceof Map) {
    const clone = new Map();
    hash.set(obj, clone);
    obj.forEach((value, key) => {
      clone.set(deepClone(key, hash), deepClone(value, hash));
    });
    return clone;
  }
  if (obj instanceof Set) {
    const clone = new Set();
    hash.set(obj, clone);
    obj.forEach(value => {
      clone.add(deepClone(value, hash));
    });
    return clone;
  }
  if (obj instanceof ArrayBuffer) return obj.slice(0);
  if (ArrayBuffer.isView(obj)) {
    return new obj.constructor(
      deepClone(obj.buffer, hash),
      obj.byteOffset,
      obj.byteLength
    );
  }
  
  // 处理循环引用
  if (hash.has(obj)) return hash.get(obj);
  
  // 处理普通对象和数组
  const proto = Object.getPrototypeOf(obj);
  const clone = Object.create(proto);
  hash.set(obj, clone);
  
  // 复制Symbol属性和普通属性
  Reflect.ownKeys(obj).forEach(key => {
    clone[key] = deepClone(obj[key], hash);
  });
  
  const allDesc = Object.getOwnPropertyDescriptors(obj)
        const result = Object.create(Object.getPrototypeOf(obj), allDesc)
        hash.set(obj, result)

  return clone;
}

WeakMap 的基本作用

WeakMap 是 JavaScript 中的一种特殊集合类型,与普通 Map 的主要区别在于:

键必须是对象(不能是原始值)

键是弱引用(不会阻止垃圾回收)

不可枚举(没有方法能获取所有键)

解决循环拷贝问题

在实现深拷贝时,遇到循环引用(对象属性间接或直接引用自身)会导致无限递归。WeakMap 可以优雅地解决这个问题:

解决方案原理
跟踪已拷贝对象:使用 WeakMap 记录原始对象和其拷贝的对应关系

遇到已拷贝对象直接返回:避免无限递归

自动内存管理:WeakMap 的弱引用特性不会阻止原始对象被垃圾回收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值