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引擎优化) |
| 空值处理 | null 或 undefined 会报错 |
会忽略 null 或 undefined |
| 可读性 | 较老的方式,代码较长 | 更现代,语法更简洁 |
详细对比和示例
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);
最佳实践建议
- 优先使用扩展运算符,语法更简洁直观
- 明确知道是浅拷贝,不要误以为是深拷贝
- 需要深拷贝时,根据需求选择合适的深拷贝方案
- 合并配置时,后面的属性会覆盖前面的(两者相同)
js
// 推荐:使用扩展运算符
const config = { ...defaultConfig, ...userConfig, ...overrideConfig };
// 或者当目标对象已存在时,使用Object.assign
const existingObj = { a: 1 };
Object.assign(existingObj, newProps); // 修改现有对象