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); // 修改现有对象
相关推荐
天人合一peng17 分钟前
Unity中button 和toggle监听事件函数有无参数
前端·unity·游戏引擎
方也_arkling1 小时前
别名路径联想提示。@/统一文件路径的配置
前端·javascript
毕设源码-朱学姐1 小时前
【开题答辩全过程】以 基于web教师继续教育系统的设计与实现为例,包含答辩的问题和答案
前端
web打印社区1 小时前
web-print-pdf:突破浏览器限制,实现专业级Web静默打印
前端·javascript·vue.js·electron·html
RFCEO2 小时前
前端编程 课程十三、:CSS核心基础1:CSS选择器
前端·css·css基础选择器详细教程·css类选择器使用方法·css类选择器命名规范·css后代选择器·精准选中嵌套元素
Amumu121382 小时前
Vuex介绍
前端·javascript·vue.js
We་ct2 小时前
LeetCode 54. 螺旋矩阵:两种解法吃透顺时针遍历逻辑
前端·算法·leetcode·矩阵·typescript
2601_949480063 小时前
【无标题】
开发语言·前端·javascript
css趣多多3 小时前
Vue过滤器
前端·javascript·vue.js
理人综艺好会3 小时前
Web学习之用户认证
前端·学习