You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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;
}

方案说明

  1. 箭头函数处理:类中定义的箭头函数this初始绑定到原始实例,通过遍历实例属性,将箭头函数替换为新的包装函数,让其内部调用时使用代理对象作为this。
  2. 方法绑定:对于普通方法,在get钩子中返回绑定了代理对象的版本,确保通过代理调用方法时,内部this指向代理对象。
  3. 异步变更支持:只要异步操作中的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 01:05:18