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); // 修改现有对象
相关推荐
前端摸鱼匠25 分钟前
Vue 3 的v-bind合并行为:讲解v-bind与普通属性合并的规则
前端·javascript·vue.js·前端框架·ecmascript
REDcker1 小时前
浏览器端Web程序性能分析与优化实战 DevTools指标与工程清单
开发语言·前端·javascript·vue·ecmascript·php·js
donecoding2 小时前
一个 sudo 引发的血案:npm 全局包权限错乱彻底修复
前端·node.js·前端工程化
风骏时光牛马2 小时前
Raku正则匹配与数据批量处理实操案例
前端
nbwenren2 小时前
2026实测:Gemini 3 镜像站视觉能力实践——拍照原型图,一键生成 HTML+CSS 代码
前端·css·html
Lee川2 小时前
Prisma 实战指南:像搭积木一样设计古诗词数据库
前端·数据库·后端
jinanwuhuaguo3 小时前
(第二十九篇)OpenClaw 实时与具身的跃迁——从异步孤岛到数字世界的“原住民”
前端·网络·人工智能·重构·openclaw
广州华水科技3 小时前
深度测评2026年单北斗GNSS位移监测系统推荐,与高口碑变形监测设备一同引领行业新风尚
前端
Alice-YUE4 小时前
【js高频八股】防抖与节流
开发语言·前端·javascript·笔记·学习·ecmascript