JavaScript Object.assign对访问器属性的拷贝处理
在JavaScript开发中,你可能觉得 Object.assign 就是个简单的“复制”方法,把源对象的属性一股脑儿搬到目标对象上就完事了。但其实,它有个很容易踩坑的行为——对访问器属性(就是那些通过 get/set 定义的属性)的处理,远没有你想的那么“智能”。说白了,它只复制属性值,不复制“行
在JavaScript开发中,你可能觉得 Object.assign 就是个简单的“复制”方法,把源对象的属性一股脑儿搬到目标对象上就完事了。但其实,它有个很容易踩坑的行为——对访问器属性(就是那些通过 get/set 定义的属性)的处理,远没有你想的那么“智能”。说白了,它只复制属性值,不复制“行为”。

先明确一个核心结论:Object.assign 在拷贝过程中,既不会调用源对象上的 getter,也不会在目标对象上保留 setter。它执行的是纯粹的“属性值”浅拷贝,对访问器属性的处理方式很特殊,也容易被忽略。
访问器属性会被转为数据属性
如果源对象的某个属性是用 get / set 定义的访问器属性,Object.assign 会读取该属性当前的返回值——也就是调用一次 getter,把拿到的值当作普通数据属性直接赋给目标对象。结果就是:目标对象上对应的键变成了一个可读写的普通数据属性,再也没有 getter/setter 行为了。
- 源对象的 getter 不会被复制,只有它的返回值被“快照”式地写进目标对象
- 源对象的 setter 完全丢失,目标对象上同名属性再也无法触发任何 setter 逻辑
- 后续你对目标对象该属性的修改,完全不影响源对象,也不触发任何访问器逻辑
无法继承或还原访问器定义
这种行为背后的机制很简单:Object.assign 基于 [[Get]] 和 [[Set]] 内部操作,而不是 Object.getOwnPropertyDescriptor 加 Object.defineProperty。所以它不会保留属性的 configurable、enumerable、writable 等特性,更别提 get / set 函数本身了。
- 即便源属性是不可枚举的,只要它能被
for...in或Object.keys遍历到(即enumerable: true),就会被拷贝 - 如果你想完整保留访问器定义,必须手动使用
Object.getOwnPropertyDescriptors配合Object.defineProperties
实际例子说明行为差异
来看一个具体的例子:
const src = {
_value: 42,
get foo() { return this._value * 2; },
set foo(v) { this._value = v / 2; }
};
const target = {};
Object.assign(target, src);
console.log(target.foo); // 84(getter 被执行一次,结果被固化)
target.foo = 100;
console.log(target.foo); // 100(已变成普通数据属性,不再触发 setter)
console.log(src._value); // 42(未改变,setter 没生效)
看到没?target.foo 现在是一个纯数据属性,和 src 的访问器逻辑彻底脱钩。你后续怎么改它,都不会影响源对象,因为 setter 已经完全丢失了。
需要保留访问器时的替代方案
如果你要深拷贝,或者需要保留访问器以及属性描述符等元信息,那就得换一种方式,别直接上 Object.assign:
- 用
Object.getOwnPropertyDescriptors(src)获取完整的属性描述符 - 再用
Object.defineProperties(target, descriptors)把它们复制过去 - 注意:这仍然是浅拷贝,如果嵌套对象或访问器内部逻辑比较复杂,还需要额外处理
