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. 类型检测:优先处理已知类型提升性能

总结

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

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

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

相关推荐
江沉晚呤时9 分钟前
C# 状态模式深度解析:构建灵活的状态驱动系统
开发语言·javascript·数据库·ui·ajax·c#·ecmascript
2401_8979300621 分钟前
CSS 从入门到精通
前端
还是鼠鼠28 分钟前
Node.js局部生效的中间件
javascript·vscode·中间件·node.js·json·express
好_快1 小时前
Lodash源码阅读-baseProperty
前端·javascript·源码阅读
每一天,每一步1 小时前
echarts生成3D立体地图react组件
javascript·react.js·echarts
BillKu1 小时前
java后端对时间进行格式处理
java·开发语言·前端
excel1 小时前
webpack 格式化模块工厂 第 二 节
前端
excel1 小时前
webpack 格式化模块工厂 第 三 节
前端
小石潭记丶1 小时前
sqlalchemy查询json
java·前端·json
好_快3 小时前
Lodash源码阅读-baseMatchesProperty
前端·javascript·源码阅读