跳到主要内容

深浅拷贝

浅拷贝复制了什么?​

  • 语义: 复制对象或数组的引用地址, 而不是其内部值;
  • 结果: 新容器与旧容器共享同一批子对象, 修改子对象会同时可见;
  • 常用手段: 扩展运算符 ...、Object.assign、Array.prototype.slice、Array.prototype.concat;
const arr1 = [{}];
const arr2 = [...arr1];
arr1[0].foo = "bar";
console.log(arr2[0]); // { foo: 'bar' }; 子对象仍共享
  • 集合互操作: ... 依赖默认迭代器, 可在数组、类型化数组、Map、Set 之间转换, 详见 迭代器;

JSON 方案做深拷贝有什么限制?​

  • 做法: JSON.parse(JSON.stringify(obj));
  • 优点: 简单, 无需递归;
  • 限制: 会丢失 undefined、函数与 symbol, 不能处理循环引用, 且会改变 Date、Map、Set 等类型的语义;
const obj1 = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(obj1));

structuredClone 适合什么场景?​

  • 做法: 调用内建 structuredClone(value), 支持循环引用与多数内建类型;
  • 限制: 旧运行环境可能不支持, 且不能克隆函数、DOM 节点等不可序列化值;

如何手写能处理循环引用的深拷贝?​

  • 思路: 递归复制, 用 WeakMap 记录已复制的对象, 再次遇到时直接返回对应副本;
const deepClone = (obj, hash = new WeakMap()) => {
if (typeof obj !== "object" || obj === null) return obj;
if (hash.has(obj)) return hash.get(obj);

const newObj = Array.isArray(obj) ? [] : {};
hash.set(obj, newObj);
for (const key in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
newObj[key] = deepClone(obj[key], hash);
}
return newObj;
};
  • 循环引用: obj1.obj2 = obj2; obj2.obj1 = obj1 也能正确复制, 因为 WeakMap 缓存了中间结果;