如何通过Proxy检测类实例的变更(含内部方法变更场景)
如何在指定约束下检测任意对象的所有变更?
需求描述
我们需要实现一套机制,能检测任意对象的变更并做出响应,同时可拦截对象创建流程。现有代码框架如下:
function withChangeDetector(factory) { const obj = factory(); return attachChangeListener(obj, () => console.log("Change occurred")); } const myObject = withChangeDetector(() => new MyObject()); doSomethingThatMayChangeMyObject(myObject); // 若上述方法在未来任意时刻修改了myObject,应输出"Change occurred"日志
约束条件
- 无法控制
factory方法,它可创建任意类型的对象 - 无法控制
doSomethingThatMayChangeMyObject方法 - 仅能修改
withChangeDetector和attachChangeListener函数 - 禁止使用轮询方式检测变更
初始Proxy方案的局限
最初采用Proxy实现变更检测,但存在明显缺陷:
function attachChangeListener(obj, onChange) { // 暂不考虑嵌套对象的情况,这部分可解决但不在本次问题范围内 return new Proxy(obj, { set: (target, key, val) => { const res = Reflect.set(target, key, val); onChange(); return res; } }); }
该方案仅能检测外部对对象的直接修改,无法覆盖类实例的内部变更。例如存在如下类时:
class MyObject { prop; setProp = (val) => this.prop = val; }
调用setProp时,函数内部的this指向原始实例而非代理对象,导致Proxy的set钩子无法触发;此外,即使通过get钩子拦截方法调用,也无法检测异步内部变更(比如setProp包含定时器的场景)。
解决方案:绑定代理对象到实例的this与方法
要解决内部this指向问题,需确保实例所有方法(包括箭头函数属性)中的this都指向代理对象,同时拦截所有可能的属性修改操作。具体实现如下:
function attachChangeListener(obj, onChange) { const proxy = new Proxy(obj, { get(target, key) { const value = Reflect.get(target, key); // 拦截函数/方法,将this绑定到proxy if (typeof value === 'function') { return function(...args) { return value.apply(proxy, args); }; } return value; }, set(target, key, val) { const oldVal = Reflect.get(target, key); const res = Reflect.set(target, key, val); // 仅当值确实发生变化时触发回调(可选优化) if (oldVal !== val) { onChange(); } return res; } }); // 处理实例上的箭头函数属性(箭头函数this为词法绑定,初始指向原始实例) for (const key of Object.getOwnPropertyNames(obj)) { const value = obj[key]; // 箭头函数无prototype,以此区分普通函数 if (typeof value === 'function' && !value.prototype) { obj[key] = function(...args) { return value.apply(proxy, args); }; } } return proxy; }
方案说明
- 箭头函数处理:类中定义的箭头函数
this初始绑定到原始实例,通过遍历实例属性,将箭头函数替换为新的包装函数,让其内部调用时使用代理对象作为this。 - 方法绑定:对于普通方法,在
get钩子中返回绑定了代理对象的版本,确保通过代理调用方法时,内部this指向代理对象。 - 异步变更支持:只要异步操作中的
this指向代理对象,当异步逻辑修改属性时,就会触发Proxy的set钩子,从而检测到变更。
验证测试
class MyObject { prop; setProp = (val) => { setTimeout(() => { this.prop = val; }, 100); }; } const myObject = withChangeDetector(() => new MyObject()); myObject.setProp('new value'); // 100ms后会输出"Change occurred"
内容的提问来源于stack exchange,提问作者Tiddo
相关产品推荐
相关产品推荐

