【js篇】JavaScript 中的包装类型详解Object.assign()与扩展运算符(...)是深拷贝还是浅拷贝?两者有何区别?

166 阅读2分钟

在 JavaScript 中,对象和数组的赋值默认是引用传递。为了创建副本,我们常使用 Object.assign() 和扩展运算符(...),但它们都只是执行 浅拷贝(Shallow Copy)


✅ 一、什么是浅拷贝?

浅拷贝是指创建一个新对象,其属性值是对原对象中属性的复制。如果属性值是基本类型,则复制的是值;如果是引用类型(如对象或数组),则复制的是引用地址。

这意味着:

  • 基本类型:独立副本;
  • 引用类型:共享同一个内存地址;

✅ 二、示例说明:都是浅拷贝

示例 1:使用扩展运算符(...

let outObj = { inObj: { a: 1, b: 2 } };
let newObj = { ...outObj };

newObj.inObj.a = 2;

console.log(outObj); // { inObj: { a: 2, b: 2 } }

✅ 结果说明:修改了 newObj.inObj.aoutObj.inObj.a 也被改变了,说明 inObj 是引用共享的。


示例 2:使用 Object.assign()

let outObj = { inObj: { a: 1, b: 2 } };
let newObj = Object.assign({}, outObj);

newObj.inObj.a = 2;

console.log(outObj); // { inObj: { a: 2, b: 2 } }

✅ 同样说明:两个对象中的 inObj 指向同一块内存区域,是浅拷贝。


✅ 三、两者的主要区别

特性Object.assign()扩展运算符(...
是否为浅拷贝✅ 是✅ 是
是否触发 setter✅ 是(会调用源对象上的 getter 和目标对象的 setter)❌ 否(直接赋值,不触发 setter)
支持继承属性❌ 不复制原型链上的属性❌ 不复制原型链上的属性
支持 Symbol 属性✅ 是✅ 是
可读性⚠️ 稍显冗长✅ 更简洁直观
修改目标对象✅ 是(第一个参数会被修改)❌ 否(返回新对象)

✅ 四、如何实现深拷贝?

由于 Object.assign()... 都是浅拷贝,对于嵌套对象/数组无法完全隔离引用关系。

实现方式包括:

1. 使用递归函数手动深拷贝(适合简单结构)

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  const copy = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepClone(obj[key]);
    }
  }
  return copy;
}

2. 使用 JSON 序列化(局限性大)

const newObj = JSON.parse(JSON.stringify(oldObj));

⚠️ 缺点:

  • 会丢失函数、undefined、Symbol、循环引用等;
  • Date 对象会被转成字符串;

3. 使用第三方库(推荐)

  • lodash.cloneDeep()
  • structuredClone()(现代浏览器支持)
  • immer.js(用于不可变数据更新)

✅ 五、一句话总结

Object.assign() 和扩展运算符(...)都是浅拷贝,只复制一层属性。对于嵌套的对象或数组,内部引用仍指向原对象,修改其中一个会影响另一个。

  • Object.assign() 会修改目标对象,并触发 setter;
  • 扩展运算符不会修改原对象,语法更简洁;
  • 如需深拷贝,建议使用递归、JSON 转换或第三方库。

💡 进阶建议

  • 在 Vue / React 开发中使用扩展运算符来创建不可变状态;
  • 使用 TypeScript 时注意区分 readonly 和可变对象;
  • 使用 ESLint 规则避免误用浅拷贝导致状态污染;
  • 推荐使用 lodash.cloneDeep()structuredClone() 实现真正深拷贝;