实现深拷贝简洁版本

手写一个简洁版的深拷贝函数,处理对象、数组等常见数据类型的深层复制

问题

在 JavaScript 中,直接赋值或浅拷贝只会复制对象的引用,修改新对象会影响原对象。深拷贝需要递归地复制对象的所有层级,创建一个完全独立的副本。本题要求实现一个简洁版的深拷贝函数,能够处理常见的数据类型。

解答

/**
 * 深拷贝函数(简洁版)
 * @param {*} obj - 需要拷贝的对象
 * @param {WeakMap} hash - 用于存储已拷贝的对象,解决循环引用问题
 * @returns {*} 拷贝后的新对象
 */
function deepClone(obj, hash = new WeakMap()) {
  // 处理 null 或 undefined
  if (obj === null || obj === undefined) {
    return obj;
  }
  
  // 处理基本数据类型
  if (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);
  }
  
  // 根据原对象的构造函数创建新对象(保持原型链)
  const cloneObj = new obj.constructor();
  
  // 存储到 hash 中,用于处理循环引用
  hash.set(obj, cloneObj);
  
  // 递归拷贝对象的所有属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  
  return cloneObj;
}

使用示例

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

const clonedObj1 = deepClone(obj1);
clonedObj1.hobbies.push('游泳');
clonedObj1.address.city = '上海';

console.log(obj1.hobbies); // ['读书', '旅游']
console.log(obj1.address.city); // '北京'
console.log(clonedObj1.hobbies); // ['读书', '旅游', '游泳']
console.log(clonedObj1.address.city); // '上海'

// 示例2:处理循环引用
const obj2 = { name: '李四' };
obj2.self = obj2; // 循环引用

const clonedObj2 = deepClone(obj2);
console.log(clonedObj2.self === clonedObj2); // true
console.log(clonedObj2 === obj2); // false

// 示例3:处理特殊对象
const obj3 = {
  date: new Date('2024-01-01'),
  regex: /test/gi,
  arr: [1, 2, { a: 3 }]
};

const clonedObj3 = deepClone(obj3);
console.log(clonedObj3.date instanceof Date); // true
console.log(clonedObj3.regex instanceof RegExp); // true
console.log(clonedObj3.arr[2] === obj3.arr[2]); // false

关键点

  • 类型判断:首先判断是否为 null、undefined 或基本数据类型,这些情况直接返回原值

  • 特殊对象处理:针对 Date、RegExp 等特殊对象,使用对应的构造函数创建新实例

  • 循环引用处理:使用 WeakMap 存储已拷贝的对象,避免无限递归。WeakMap 的键是弱引用,不会阻止垃圾回收

  • 保持原型链:使用 new obj.constructor() 创建新对象,保持与原对象相同的原型链

  • 递归拷贝:使用 for...in 遍历对象的可枚举属性,配合 hasOwnProperty 过滤原型链上的属性,递归调用深拷贝函数

  • 简洁性权衡:此版本未处理 Map、Set、Symbol 等复杂类型,如需完整版本可进一步扩展