Object.assign和扩展运算符是深拷贝还是浅拷贝,两者有什么区别

Object.assign() 和扩展运算符(Spread Operator ...)都是浅拷贝(Shallow Copy) ,不是深拷贝(Deep Copy)。它们只复制对象的第一层属性,嵌套对象仍然是引用。

两者相同点:都是浅拷贝

js 复制代码
// 示例:都是浅拷贝
const original = {
  name: "张三",
  address: {
    city: "北京",
    street: "长安街"
  }
};

// 使用 Object.assign
const copy1 = Object.assign({}, original);
// 使用扩展运算符
const copy2 = { ...original };

// 修改原始对象的嵌套属性
original.address.city = "上海";

console.log(copy1.address.city); // "上海" - 被影响了
console.log(copy2.address.city); // "上海" - 被影响了

// 但第一层属性是独立的
original.name = "李四";
console.log(copy1.name); // "张三" - 不受影响
console.log(copy2.name); // "张三" - 不受影响

主要区别对比表

特性 Object.assign() 扩展运算符 ...
语法 函数形式:Object.assign(target, source1, source2...) 运算符:{ ...source1, ...source2 }
合并对象 支持多个源对象 支持多个源对象
返回值 返回目标对象 返回新对象
原型链属性 不复制原型链上的属性 不复制原型链上的属性
Symbol属性 复制Symbol属性 复制Symbol属性
可枚举性 只复制可枚举属性 只复制可枚举属性
性能 相对较慢 相对较快(现代JS引擎优化)
空值处理 nullundefined 会报错 会忽略 nullundefined
可读性 较老的方式,代码较长 更现代,语法更简洁

详细对比和示例

1. 基本用法差异

js 复制代码
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

// Object.assign
const merged1 = Object.assign({}, obj1, obj2);
// 扩展运算符
const merged2 = { ...obj1, ...obj2 };

console.log(merged1); // { a: 1, b: 2, c: 3, d: 4 }
console.log(merged2); // { a: 1, b: 2, c: 3, d: 4 }

2. 处理 null/undefined 的区别

js 复制代码
// Object.assign 遇到 null 或 undefined 会报错
try {
  const result1 = Object.assign({}, null);
} catch (e) {
  console.log(e); // TypeError: Cannot convert undefined or null to object
}

// 扩展运算符会忽略 null 或 undefined
const result2 = { ...null, ...undefined, a: 1 };
console.log(result2); // { a: 1 } - null/undefined 被忽略

3. 合并多个对象的差异

js 复制代码
const objA = { a: 1 };
const objB = { b: 2 };
const objC = { c: 3 };

// Object.assign - 直接合并多个
const mergedA = Object.assign({}, objA, objB, objC);

// 扩展运算符 - 需要显式展开多个
const mergedB = { ...objA, ...objB, ...objC };

// 复杂合并示例
const defaults = { theme: "dark", fontSize: 14 };
const userSettings = { fontSize: 16 };
const override = { theme: "light" };

// 两种方式都可以实现配置合并
const settings1 = Object.assign({}, defaults, userSettings, override);
const settings2 = { ...defaults, ...userSettings, ...override };

console.log(settings1); // { theme: "light", fontSize: 16 }
console.log(settings2); // { theme: "light", fontSize: 16 }

4. 原型链和不可枚举属性

js 复制代码
const proto = { protoProp: "proto" };
const obj = Object.create(proto, {
  enumerableProp: { value: "enumerable", enumerable: true },
  nonEnumerableProp: { value: "non-enumerable", enumerable: false }
});

// 两者都不复制原型链属性和不可枚举属性
const copy1 = Object.assign({}, obj);
const copy2 = { ...obj };

console.log(copy1); // { enumerableProp: "enumerable" }
console.log(copy2); // { enumerableProp: "enumerable" }
console.log(copy1.protoProp); // undefined
console.log(copy2.protoProp); // undefined

5. 对数组的使用

js 复制代码
// 扩展运算符对数组更自然
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 扩展运算符合并数组
const mergedArr1 = [...arr1, ...arr2];

// Object.assign 也可以用于数组,但不太直观
const mergedArr2 = Object.assign([], arr1, arr2);
// 注意:Object.assign用于数组时,会把数组当作对象处理

console.log(mergedArr1); // [1, 2, 3, 4, 5, 6]
console.log(mergedArr2); // [1, 2, 3, 4, 5, 6]

6. 性能考虑

在现代 JavaScript 引擎中,扩展运算符通常比 Object.assign() 性能更好,尤其是在创建新对象时:

js 复制代码
// 扩展运算符通常更快
const data = { /* 大量数据 */ };

// 方式1:扩展运算符(推荐)
const fastCopy = { ...data };

// 方式2:Object.assign
const slowCopy = Object.assign({}, data);

如何实现深拷贝?

如果你需要真正的深拷贝,可以使用以下方法:

1. JSON 方法(有局限性)

js 复制代码
const deepCopy = JSON.parse(JSON.stringify(original));
// 缺点:不能复制函数、Symbol、undefined、循环引用等

2. 递归函数

js 复制代码
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof Array) return obj.map(item => deepClone(item));
  
  const cloned = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloned[key] = deepClone(obj[key]);
    }
  }
  return cloned;
}

3. 第三方库

js 复制代码
// Lodash
const cloned = _.cloneDeep(original);

// 或使用现代浏览器内置的API(实验性)
const cloned = structuredClone(original);

最佳实践建议

  1. 优先使用扩展运算符,语法更简洁直观
  2. 明确知道是浅拷贝,不要误以为是深拷贝
  3. 需要深拷贝时,根据需求选择合适的深拷贝方案
  4. 合并配置时,后面的属性会覆盖前面的(两者相同)
js 复制代码
// 推荐:使用扩展运算符
const config = { ...defaultConfig, ...userConfig, ...overrideConfig };

// 或者当目标对象已存在时,使用Object.assign
const existingObj = { a: 1 };
Object.assign(existingObj, newProps); // 修改现有对象
相关推荐
AI前端老薛7 分钟前
CSS实现动画的几种方式
前端·css
晨米酱9 分钟前
轻量级 Git Hooks 管理工具 Husky
前端·代码规范
携欢11 分钟前
portswigger靶场之修改序列化数据类型通关秘籍
android·前端·网络·安全
GuMoYu12 分钟前
npm link 测试本地依赖完整指南
前端·npm
代码老祖13 分钟前
vue3 vue-pdf-embed实现pdf自定义分页+关键词高亮
前端·javascript
未等与你踏清风13 分钟前
Elpis npm 包抽离总结
前端·javascript
代码猎人14 分钟前
如何使用for...of遍历对象
前端
秋天的一阵风15 分钟前
🎥解决前端 “复现难”:rrweb 录制回放从入门到精通(下)
前端·开源·全栈
林恒smileZAZ15 分钟前
【Vue3】我用 Vue 封装了个 ECharts Hooks
前端·vue.js·echarts
颜酱16 分钟前
用填充表格法-继续吃透完全背包及其变形
前端·后端·算法