JavaScript 拷贝技术全解析:从浅拷贝到深拷贝

一、拷贝的本质与分类

拷贝的本质是基于原对象创建新对象。根据对嵌套结构的处理方式分为两类:

  1. 浅拷贝:仅复制对象的第一层属性,子对象仍共享内存地址
  2. 深拷贝:递归复制所有层级属性,新旧对象完全独立

二、浅拷贝的实现与原理

1. 常用方法

javascript 复制代码
// 对象浅拷贝
const obj = { a: 1, b: { n: 2 } };
const objCopy1 = Object.assign({}, obj);
const objCopy2 = { ...obj };

// 数组浅拷贝
const arr = [1, 2, { n: 3 }];
const arrCopy1 = arr.slice();
const arrCopy2 = [...arr];

2. 原型链拷贝

javascript 复制代码
const obj = { a: 1 };
const newObj = Object.create(obj);
obj.a = 2;
console.log(newObj.a); // 输出2(共享原型属性)

3. 浅拷贝的局限性

javascript 复制代码
const original = { 
  a: 1, 
  b: { 
    inner: { n: 2 }
  }
};

const copy = Object.assign({}, original);
original.b.inner.n = 100;
console.log(copy.b.inner.n); // 输出100(子对象被修改)

三、深拷贝的实现方案

1. JSON 序列化法

javascript 复制代码
const obj = {
  name: 'John',
  like: { game: 'Chess' },
  date: new Date(),
  func: () => console.log('test'),
  sym: Symbol('key'),
  infinity: Infinity
};

const deepCopy = JSON.parse(JSON.stringify(obj));

console.log(deepCopy); 
// 输出:{ name: 'John', like: { game: 'Chess' }, date: "2024-05-15T..." }
// 丢失:函数、Symbol、Infinity 等特殊类型

缺陷

  • 无法处理:undefinedSymbolFunction
  • 转换异常:NaNnullDate → 字符串
  • 循环引用报错:obj.self = obj

2. structuredClone API

javascript 复制代码
const user = {
  name: 'Alice',
  like: { game: 'Piano' },
  tags: new Set(['music', 'art'])
};

const clone = structuredClone(user);
user.like.game = 'Guitar';

console.log(clone.like.game); // 保持'Piano'(深拷贝)
console.log(clone.tags instanceof Set); // true(保留特殊对象)

优势

支持循环引用

保留对象类型(Date、Set、Map等)

处理特殊值(NaNInfinity

限制

无法克隆函数、DOM节点

旧浏览器需polyfill


3. 手动实现深拷贝

javascript 复制代码
function deepClone(obj, cache = new WeakMap()) {
  // 处理循环引用
  if(cache.has(obj)) return cache.get(obj);

  // 处理原始类型
  if(typeof obj !== 'object' || obj === null) return obj;

  // 处理特殊对象
  const Constructor = obj.constructor;
  if(/^(Date|RegExp|Map|Set)$/i.test(Constructor.name)) {
    return new Constructor(obj);
  }

  // 创建新对象
  const clone = Array.isArray(obj) ? [] : {};
  cache.set(obj, clone);

  // 递归拷贝
  Reflect.ownKeys(obj).forEach(key => {
    clone[key] = deepClone(obj[key], cache);
  });

  return clone;
}

// 测试用例
const obj = { a: new Date(), b: [1, {n:2}] };
obj.self = obj;
const copy = deepClone(obj);

四、拷贝技术对比

方法 优点 缺点 适用场景
Object.assign 简单快速 仅浅拷贝 简单对象拷贝
JSON方法 兼容性好 丢失特殊类型 无特殊类型的纯数据对象
structuredClone 支持复杂类型 浏览器兼容性问题 现代浏览器环境
手动实现 完全可控 实现成本高 需要处理特殊类型或定制化场景

五、高级场景处理

1. 原型链属性拷贝

javascript 复制代码
function cloneWithProto(obj) {
  const proto = Object.getPrototypeOf(obj);
  const copy = Object.create(proto);
  Object.entries(obj).forEach(([key, val]) => {
    copy[key] = deepClone(val);
  });
  return copy;
}

2. 处理不可枚举属性

javascript 复制代码
function cloneAllProperties(obj) {
  const props = Object.getOwnPropertyDescriptors(obj);
  const clone = Object.create(
    Object.getPrototypeOf(obj),
    Object.entries(props).reduce((acc, [key, desc]) => {
      desc.value = deepClone(desc.value);
      acc[key] = desc;
      return acc;
    }, {})
  );
  return clone;
}

六、性能优化建议

  1. 对象冻结 :对不需要修改的对象使用Object.freeze()
  2. 拷贝缓存:使用WeakMap记录已拷贝对象
  3. 按需拷贝:仅拷贝实际用到的属性
  4. 类型检测:优先处理已知类型提升性能

总结

选择拷贝方案时需要综合考量:

  • 数据类型复杂度:是否包含特殊对象
  • 性能要求:大数据量时避免深度递归
  • 运行环境:浏览器兼容性要求
  • 功能需求:是否需要保留原型链等特性

掌握不同拷贝技术的原理与适用场景,能够有效提升代码质量与执行效率。

相关推荐
恋猫de小郭30 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端