Object.assign 和对象展开语法有什么区别?
简化版
Object.assign(target, ...sources) 会把源对象自身可枚举属性复制到目标对象,并返回目标对象;对象展开 { ...obj } 常用于创建新对象并浅拷贝属性。
两者都是浅拷贝,都会触发源对象 getter,也都只复制可枚举自有属性。关键区别在于 Object.assign 会调用目标对象的 setter,而对象展开定义的是新对象属性,行为更接近创建新对象。
详细版
const source = { a: 1 };
const assigned = Object.assign({}, source);
const spread = { ...source };
都是浅拷贝:
const source = { nested: { x: 1 } };
const copy = { ...source };
copy.nested.x = 2;
console.log(source.nested.x); // 2
setter 差异:
const target = {
set x(value) {
console.log("setter", value);
}
};
Object.assign(target, { x: 1 }); // 触发 setter
const obj = { ...target, x: 1 }; // 创建/定义属性
实际项目中,普通合并配置更常用对象展开;需要明确写入已有目标对象时才用 Object.assign。
完整版教学
一、先明确两者的共同点
Object.assign 和对象展开都不是深拷贝。
它们复制的是源对象自身可枚举属性的当前值。
source own enumerable properties
-> read value
-> write/define on result
面试里最容易先问浅拷贝,再追问 getter、setter、原型和属性描述符。
二、Object.assign 会修改目标对象
const target = { a: 1 };
const result = Object.assign(target, { b: 2 });
console.log(result === target); // true
console.log(target); // { a: 1, b: 2 }
如果不想改原对象,通常把 {} 放在第一个参数。
const next = Object.assign({}, oldConfig, userConfig);
三、对象展开更常用于创建新对象
const next = {
...defaultConfig,
...userConfig,
debug: false
};
后面的属性会覆盖前面的同名属性。
数字例子:
const obj = { ...{ a: 1 }, ...{ a: 2 }, b: 3 };
// { a: 2, b: 3 }
四、浅拷贝的边界
嵌套对象只复制引用。
const a = { list: [1, 2] };
const b = { ...a };
b.list.push(3);
console.log(a.list); // [1, 2, 3]
如果需要更新嵌套状态,要逐层复制变化路径,或使用结构共享工具。
五、getter 和 setter 的差异
读取源对象属性时,两者都会触发 getter。
const source = {
get value() {
console.log("get");
return 1;
}
};
({ ...source });
Object.assign({}, source);
写入目标时,Object.assign 使用赋值语义,可能触发 setter;对象展开在新对象上创建属性。
六、属性描述符不会完整保留
const source = {};
Object.defineProperty(source, "id", {
value: 1,
enumerable: true,
writable: false
});
const copy = { ...source };
copy.id 是普通数据属性,不再保留原来的 writable: false。若要保留描述符,应使用 Object.getOwnPropertyDescriptors。
| 需求 | 更合适的方式 |
|---|---|
| 普通配置合并 | 对象展开 |
| 写入已有对象 | Object.assign |
| 保留属性描述符 | Object.defineProperties |
七、常见误区与追问
- 误区:对象展开就是深拷贝。 它只复制第一层属性值。
- 误区:Object.assign 不会改任何入参。 它会修改第一个目标对象。
- 误区:两者会复制原型属性。 它们只复制自身可枚举属性。
- 误区:属性描述符会原样复制。 普通拷贝拿的是值,不保留完整描述符。
- 追问:为什么 assign 会触发 setter? 因为它向目标对象执行赋值操作。
- 追问:多个源对象同名字段谁生效? 后面的源覆盖前面的同名字段。
八、加强记忆
记住一句工程判断:
need new object -> spread
need mutate target -> Object.assign
need descriptors -> defineProperties
答题时先讲共同点:浅拷贝、自有可枚举、后者覆盖前者;再讲差异:是否修改目标对象、setter 行为、描述符保留问题。