手写深拷贝

实现支持循环引用和特殊类型的深拷贝函数

问题

手写一个深拷贝函数,要求:

  • 处理循环引用
  • 正确拷贝 Date、RegExp、Symbol 等特殊类型
  • 支持数组和普通对象

解答

基础版本

function deepClone(obj, map = new WeakMap()) {
  // 处理 null 和非对象类型
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理循环引用
  if (map.has(obj)) {
    return map.get(obj);
  }

  // 处理 Date
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }

  // 处理 RegExp
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }

  // 创建新对象或数组
  const clone = Array.isArray(obj) ? [] : {};
  
  // 存入 map,处理循环引用
  map.set(obj, clone);

  // 递归拷贝属性
  for (const key of Reflect.ownKeys(obj)) {
    clone[key] = deepClone(obj[key], map);
  }

  return clone;
}

完整版本(支持更多类型)

function deepClone(obj, map = new WeakMap()) {
  // 处理 null 和非对象类型
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理循环引用
  if (map.has(obj)) {
    return map.get(obj);
  }

  // 获取对象类型
  const type = Object.prototype.toString.call(obj);

  // 处理特殊类型
  switch (type) {
    case '[object Date]':
      return new Date(obj.getTime());
    
    case '[object RegExp]':
      return new RegExp(obj.source, obj.flags);
    
    case '[object Map]': {
      const clone = new Map();
      map.set(obj, clone);
      obj.forEach((value, key) => {
        clone.set(deepClone(key, map), deepClone(value, map));
      });
      return clone;
    }
    
    case '[object Set]': {
      const clone = new Set();
      map.set(obj, clone);
      obj.forEach(value => {
        clone.add(deepClone(value, map));
      });
      return clone;
    }
    
    case '[object ArrayBuffer]':
      return obj.slice(0);
  }

  // 处理数组和普通对象
  const clone = Array.isArray(obj) ? [] : {};
  map.set(obj, clone);

  // 使用 Reflect.ownKeys 获取所有属性(包括 Symbol)
  for (const key of Reflect.ownKeys(obj)) {
    clone[key] = deepClone(obj[key], map);
  }

  return clone;
}

测试用例

// 测试循环引用
const obj1 = { name: 'test' };
obj1.self = obj1;
const cloned1 = deepClone(obj1);
console.log(cloned1.self === cloned1); // true
console.log(cloned1 !== obj1); // true

// 测试特殊类型
const obj2 = {
  date: new Date('2024-01-01'),
  regex: /test/gi,
  [Symbol('key')]: 'symbol value',
  map: new Map([['a', 1]]),
  set: new Set([1, 2, 3])
};
const cloned2 = deepClone(obj2);
console.log(cloned2.date instanceof Date); // true
console.log(cloned2.date !== obj2.date); // true
console.log(cloned2.regex.test('test')); // true

关键点

  • WeakMap 处理循环引用:用 WeakMap 存储已拷贝对象,遇到重复引用直接返回
  • Reflect.ownKeys 获取所有键:包括 Symbol 类型的键,Object.keys 无法获取 Symbol
  • 特殊对象单独处理:Date、RegExp、Map、Set 需要用各自的构造函数重新创建
  • 递归拷贝:对象属性值可能还是对象,需要递归处理
  • 类型判断用 toStringObject.prototype.toString.call()instanceof 更准确