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); // 修改现有对象
相关推荐
计算机魔术师6 分钟前
OpenAI 回应智能体接管德语维基网站事件,称将改革 AI 误对齐事件披露机制
前端
晴天1618 分钟前
PWA 让网页拥有原生应用的体验
前端·pwa
雪芽蓝域zzs18 分钟前
第十七节:面包屑导航组件(适配多级嵌套路由)
前端·javascript·vue.js
Csvn44 分钟前
类型驱动开发(Type-Driven Development):让类型约束先行为导向
前端
计算机魔术师1 小时前
AI 代理把德国维基当留言板,OpenAI 闷声几周才承认
前端
yume_sibai1 小时前
Element Plus 其他实用组件完全指南(10个核心组件)
前端·javascript·vue.js
雪芽蓝域zzs1 小时前
第二十二节:进阶:用户管理新增、编辑弹窗 + 表单校验
前端·javascript·vue.js
IT_陈寒1 小时前
Java字符串判等踩坑记:==和equals真的不能乱用
前端·人工智能·后端
晴天161 小时前
前端虚拟滚动(虚拟列表)原理与实战
前端
晴天162 小时前
前端 RAIL 性能模型实战分享-Day37
前端