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); // 修改现有对象
相关推荐
暖和_白开水2 分钟前
数据分析agent (七):contextvars 模块上下文request_id
java·前端·数据分析
程序员黑豆10 分钟前
鸿蒙应用开发:Flex 组件从入门到实战
前端·华为·harmonyos
大爱编程♡11 分钟前
Vue3+TypeScript+element-plus的文章管理项目(配后端接口)-退出及文章管理页面
前端·javascript·typescript
先吃饱再说12 分钟前
React 组件设计:从 Props 传递到组件封装
前端·react.js
独立开阀者_FwtCoder15 分钟前
最近做了一个健身小程序:智形健身助手,健身的佬们来提点意见
前端·javascript·github
舒灿19 分钟前
喵ing Tab 1.0.0 正式发布
前端·ai编程
葡萄城技术团队22 分钟前
当表格数据来自外部系统:SpreadJS 如何让异步公式一键刷新
前端
小徐_233326 分钟前
Open Wot 1.0.5 发布:让 AI 接入 wot-ui,只需要两条命令
前端·uni-app·ai编程
Android研究员30 分钟前
Android进阶之事件分发机制深度剖析
android·前端·面试
Csvn38 分钟前
🛡️ 前端水印方案全解析——从「能被 F12 删掉」到「删不掉」的反破解实战
前端