object.assign不具有原子性,执行中抛错会导致目标对象处于已部分修改的中间态;它按源对象及属性枚举顺序逐个赋值,getter/setter抛错或目标冻结时会中断,已赋值属性不可回滚。

Object.assign 的“浅拷贝原子性”其实并不存在——它不具有原子性,执行中一旦抛出错误,目标对象就处于明确的、不可回滚的中间态。
异常会中断执行,但已赋值的属性不会撤销
Object.assign 是按源对象顺序、再按属性枚举顺序逐个赋值的。如果某个源对象的某个 getter 属性在取值时抛错(例如 get b() { throw new Error() }),那么:
- 该属性之前的赋值(包括前面源对象的所有属性、以及当前源对象中已成功读取的属性)都已写入目标对象
- 报错后后续所有属性都不会再处理
- 目标对象被部分修改,且没有事务回滚机制
示例:
const target = { a: 1 };
const source = {
a: 2,
get b() { throw new Error('boom'); },
c: 3
};
try {
Object.assign(target, source);
} catch (e) {}
console.log(target); // { a: 2 } —— a 被覆盖了,b 和 c 都没进去
哪些操作可能触发中途报错
不是所有属性访问都安全。以下情况会导致赋值中断:
- 源对象某属性是 getter,且 getter 内部 throw 错误
- 目标对象某属性是 setter,且 setter 抛错(例如设置了
writable: false但尝试改值) - 目标对象为冻结对象(
Object.freeze()),而源对象试图写入新属性或修改已有属性
为什么设计成这样
Object.assign 定位是轻量级属性搬运工具,不是事务型复制引擎。它的行为符合 JavaScript 赋值语义:每次 target[key] = value 都是独立操作,失败即停止,不保证整体一致性。这也意味着:
- 不能依赖它做“全有或全无”的配置合并
- 若需强一致性,应先校验、或用 try/catch 包裹并手动清理,或换用更可控的合并逻辑(如遍历
Reflect.ownKeys+Object.getOwnPropertyDescriptor)










