object.assign 不会触发 setter,因其底层执行 target[key] = source[key] 的普通赋值;若需触发,应改用显式赋值循环或框架专用方法。

Object.assign 不会触发目标对象的 setter,这是它的设计行为,不是“坑”而是预期表现;但如果你期望 setter 被调用(比如依赖响应式更新、参数校验或副作用逻辑),就需要主动绕过默认行为——本质是避免直接赋值,改用显式调用 setter。
理解 Object.assign 的底层机制
Object.assign 本质上是遍历源对象的**可枚举自有属性**,对每个 key 执行 target[key] = source[key]。这个赋值操作走的是 JavaScript 的普通属性写入流程,不经过 Object.defineProperty 定义的 setter(除非该 setter 是在 target 自身定义、且 key 对应的属性是 accessor 属性并被显式访问)。
常见误解:以为 setter 像 Vue 2 的 defineProperty 响应式那样能拦截所有赋值——其实不能,Object.assign 的赋值不触发 accessor setter,除非目标对象该属性本身是 getter/setter 且你手动触发 set。
用 Object.defineProperty + for...in 替代 assign
若目标对象的 setter 在原型上或自身定义,且你希望每次赋值都触发它,就别用 Object.assign,改用循环 + 显式赋值:
- 遍历源对象的可枚举属性(可用
Object.keys(source)或for...in配合hasOwnProperty) - 对每个 key,执行
target[key] = source[key]—— 这个点号赋值会正常触发 setter(前提是该属性是 accessor 类型且定义在 target 或其原型链上) - 注意:如果 setter 在原型上,确保 target 未屏蔽该属性(即没有同名 data 属性)
封装安全的 assignWithSetters 工具函数
可以封装一个兼容 setter 的浅拷贝函数:
function assignWithSetters(target, ...sources) {
sources.forEach(source => {
Object.keys(source).forEach(key => {
target[key] = source[key]; // 触发 setter
});
});
return target;
}
使用示例:
const obj = {
_name: '',
set name(value) {
console.log('setter called:', value);
this._name = String(value).trim();
}
};
assignWithSetters(obj, { name: ' Alice ' }); // ✅ 输出日志,name 被处理
响应式框架场景下的正确做法
在 Vue 2/3、MobX 等响应式系统中,不要依赖 Object.assign 更新响应式对象:
- Vue 2:用
this.$set(obj, key, value)或替换整个对象(obj = {...obj, ...newData}) - Vue 3(ref/reactive):直接赋值即可(
reactiveObj.key = value),因为 Proxy 可拦截,但Object.assign(reactiveObj, src)仍不推荐——它绕过响应式系统内部 track/trigger,可能丢失依赖收集 - MobX:用
extendObservable或直接赋值(autorun 会自动追踪),避免 Object.assign
核心原则:响应式更新必须走框架认可的路径,而非原生赋值语义。
不复杂但容易忽略:setter 是否生效,取决于赋值时属性的描述符类型和赋值方式,而不是数据来源。Object.assign 只是语法糖,背后仍是简单赋值——想触发逻辑,就得让它“路过”setter。










