实现完整的深拷贝

实现一个能够处理各种数据类型和循环引用的深拷贝函数

问题

在 JavaScript 中,对象和数组的赋值是引用传递,修改新对象会影响原对象。深拷贝需要创建一个完全独立的副本,包括处理:

  • 基本数据类型和引用类型
  • 数组、对象、Date、RegExp、Map、Set 等特殊对象
  • 循环引用问题
  • 原型链的保留

解答

function deepClone(obj, hash = new WeakMap()) {
  // 处理 null 或 undefined
  if (obj === null || obj === undefined) {
    return obj;
  }

  // 处理基本数据类型
  if (typeof obj !== 'object') {
    return obj;
  }

  // 处理 Date 对象
  if (obj instanceof Date) {
    return new Date(obj);
  }

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

  // 处理 Map 对象
  if (obj instanceof Map) {
    const mapClone = new Map();
    obj.forEach((value, key) => {
      mapClone.set(key, deepClone(value, hash));
    });
    return mapClone;
  }

  // 处理 Set 对象
  if (obj instanceof Set) {
    const setClone = new Set();
    obj.forEach(value => {
      setClone.add(deepClone(value, hash));
    });
    return setClone;
  }

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

  // 创建新对象,保留原型链
  const cloneObj = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));

  // 存储到 hash 中,用于处理循环引用
  hash.set(obj, cloneObj);

  // 处理 Symbol 类型的 key
  const symbolKeys = Object.getOwnPropertySymbols(obj);
  symbolKeys.forEach(key => {
    cloneObj[key] = deepClone(obj[key], hash);
  });

  // 递归拷贝对象的所有属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }

  return cloneObj;
}

使用示例

// 示例 1: 基本对象拷贝
const obj1 = {
  name: '张三',
  age: 25,
  hobbies: ['reading', 'coding'],
  address: {
    city: '北京',
    district: '朝阳区'
  }
};

const clonedObj1 = deepClone(obj1);
clonedObj1.address.city = '上海';
console.log(obj1.address.city); // 输出: 北京
console.log(clonedObj1.address.city); // 输出: 上海

// 示例 2: 处理循环引用
const obj2 = { name: '李四' };
obj2.self = obj2;
const clonedObj2 = deepClone(obj2);
console.log(clonedObj2.self === clonedObj2); // 输出: true

// 示例 3: 处理特殊对象
const obj3 = {
  date: new Date('2024-01-01'),
  regex: /test/gi,
  map: new Map([['key1', 'value1'], ['key2', 'value2']]),
  set: new Set([1, 2, 3]),
  symbol: Symbol('test')
};

const clonedObj3 = deepClone(obj3);
console.log(clonedObj3.date instanceof Date); // 输出: true
console.log(clonedObj3.regex.test('TEST')); // 输出: true
console.log(clonedObj3.map.get('key1')); // 输出: value1

// 示例 4: 处理 Symbol 类型的 key
const symbolKey = Symbol('id');
const obj4 = {
  [symbolKey]: 123,
  name: '王五'
};

const clonedObj4 = deepClone(obj4);
console.log(clonedObj4[symbolKey]); // 输出: 123

关键点

  • 使用 WeakMap 处理循环引用:WeakMap 的 key 是弱引用,不会阻止垃圾回收,避免内存泄漏
  • 类型判断要全面:需要处理 Date、RegExp、Map、Set 等特殊对象类型
  • 保留原型链:使用 Object.create(Object.getPrototypeOf(obj)) 保留对象的原型
  • 处理 Symbol 类型的 key:使用 Object.getOwnPropertySymbols() 获取 Symbol 类型的属性
  • 使用 hasOwnProperty 过滤:只拷贝对象自身的属性,不拷贝原型链上的属性
  • 递归处理嵌套结构:对每个属性值递归调用 deepClone 函数
  • 边界条件处理:正确处理 null、undefined 和基本数据类型