Object.assign本身无响应式能力,需结合Object.defineProperty或Proxy手动实现:先合并数据,再遍历属性定义getter/setter,支持嵌套递归响应、访问器代理及更新通知。

Object.assign 本身不具备响应式能力,它只是浅拷贝属性值。若想让合并后的对象具备响应式特征(即监听属性读写、触发更新),需手动为每个目标属性注入 getter/setter,并在 setter 中触发依赖通知或更新逻辑。这本质上是模拟 Vue 2 的响应式原理,但不依赖框架。
用 Object.defineProperty 封装响应式赋值逻辑
直接使用 Object.assign 合并后,再遍历目标对象的自有属性,用 Object.defineProperty 重定义每个属性,添加 getter(返回缓存值)和 setter(更新值 + 执行回调):
- 先创建一个响应式容器(如
reactive(obj, callback)),内部保存原始值与更新函数 - 对每个 key 调用
Object.defineProperty,getter 返回当前值,setter 在赋新值后调用callback - 再用
Object.assign(target, source)初始化数据,随后立即对 target 做响应式封装
合并时保留源对象的 getter/setter 并代理到目标
若 source 对象中已有自定义访问器,Object.assign 默认只拷贝属性值,会丢失这些访问器。需改用 Object.getOwnPropertyDescriptors 获取完整描述符,再用 Object.defineProperties 合并:
- 获取 source 的所有属性描述符:
const descriptors = Object.getOwnPropertyDescriptors(source) - 遍历 descriptors,对每个有
get或set的属性,在 target 上重新定义(可选择原样代理,或包装后加入响应逻辑) - 对普通数据属性,仍可用 setter 注入响应式更新;对已有 setter 的属性,可链式调用(如
originalSetter.call(this, newVal); notify();)
处理嵌套对象:递归响应式化 + 代理拦截
原始对象合并默认不深响应。若需深层响应,可在 setter 中检测新值是否为对象,自动对其执行相同响应式封装:
- 在响应式 setter 内判断
val !== null && typeof val === 'object',若是则递归调用reactive(val, callback) - 为避免循环引用,维护一个 WeakMap 缓存已响应式化的对象,重复传入时直接返回代理对象
- 更进一步可结合
Proxy替代Object.defineProperty,天然支持动态属性、数组索引、in 操作符等(但注意兼容性)
实际使用示例(简易响应式合并函数)
以下是一个轻量实现,支持基础响应式合并与 setter 链式调用:
function reactiveMerge(target, ...sources) {
const callbacks = new Set();
const track = (cb) => callbacks.add(cb);
const trigger = () => callbacks.forEach(cb => cb());
// 递归响应式化
function defineReactive(obj) {
if (!obj || typeof obj !== 'object') return;
Object.keys(obj).forEach(key => {
let value = obj[key];
if (value && typeof value === 'object') defineReactive(value);
Object.defineProperty(obj, key, {
get() { return value; },
set(newVal) {
if (newVal !== value) {
value = newVal;
if (newVal && typeof newVal === 'object') defineReactive(newVal);
trigger();
}
},
enumerable: true,
configurable: true
});
});
}
// 先合并,再响应式化
sources.forEach(src => {
Object.assign(target, src);
});
defineReactive(target);
return { target, track };
}
// 使用
const state = {};
const { target, track } = reactiveMerge(state, { count: 0 }, { name: 'Alice' });
track(() => console.log('更新了:', target.count)); // 订阅
target.count = 5; // 输出:更新了: 5

















