为什么这个 setter 在我代理它时没有给我截获的值?

Why do this setter is not giving me intercepted values when I proxy it?

我确定我创建代理的方式是正确的,因为当我将 属性 登录到 devtools 控制台时,我可以看到 setter 已被代理。

问题不会发生在作为函数的属性上,它们的拦截工作正常。

我主要是按照这个 的回答来构建这个。不过,我必须说这个答案看起来很难理解,所以我从中剥离了很多东西以进入简单的样式,您可以从下面的代码中看到这一点。

class Hook {
    constructor(object) {
        if (object) {
            this.object = object;
        }
    }

    add(object) {
        this.object = object;
    }


    proxy(handler) {
        return new Proxy(this.object, handler);
    }
};


const hook = new Hook();

Object.getOwnPropertyNames(CanvasRenderingContext2D.prototype).forEach(function (property) {
    let propertyDescription = Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, property);

    const proxyPropertyDescription = {
        configurable: propertyDescription.configurable,
        enumerable: propertyDescription.enumerable
    }

    if (typeof propertyDescription.value === "function") {
        const handle = {
            apply(target, thisArg, args) {
                // forward invocation to underlying function
                console.log("apply", thisArg, target, args)

                return target.apply(thisArg, thisArg, args)
            }
        };

        hook.add(propertyDescription.value)

        proxyPropertyDescription.writable = propertyDescription.writable;
        proxyPropertyDescription.value = hook.proxy(handle, true);
    }

    if (propertyDescription.set) {
        const handle = {
            set(target, key, value) {

                // forward access to underlying property
                Reflect.set(target, key, value);

                console.log("set", target, key, value);
            }
        };

        hook.add(propertyDescription.set)

        proxyPropertyDescription.set = hook.proxy(handle, true);
    }

    Object.defineProperty(CanvasRenderingContext2D.prototype, property, proxyPropertyDescription);
})

var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");

// My target but it never get proxified 
ctx.font = "14px 'Arial'";

// But functions like this works....
ctx.fill("red");


// I can see that on Devtools when I log this Property has been set to Proxy
console.log(Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, "font"));

不要将 getters/setters 与代理混用。使用两者之一,而不是两者。

这是一个仅使用 属性 描述符的解决方案:

const hook = {
    wrapMethod(name, method) {
        return function(...args) {
            console.log("applying method "+name, args);
            return method.apply(this, args);
        };
    },
    wrapSetter(name, setter) {
        return function(value) {
            console.log("setting "+name, value);
            return setter.call(this, value);
        };
    }
};

const proto = CanvasRenderingContext2D.prototype;
for (const name of Object.getOwnPropertyNames(proto)) {
    const descriptor = Object.getOwnPropertyDescriptor(proto, name);

    if (!descriptor.configurable) {
        console.log("Cannot hook onto immutable ."+name);
        continue;
    } else if (typeof descriptor.value == "function") {
        descriptor.value = hook.wrapMethod(name, descriptor.value);
    } else if (descriptor.set) {
        descriptor.set = hook.wrapSetter(name, descriptor.set);
    } else {
        console.log("Did not hook onto ."+name, descriptor);
    }
    Object.defineProperty(proto, name, descriptor);
}


var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.font = "14px 'Arial'";
ctx.fillStyle = "red";
ctx.fillRect(130, 190, 40, 60);

这是一个仅使用代理的解决方案:

const hook = {
    wrapMethod(name, method) {
        return function(...args) {
            console.log("applying method "+name, args);
            return method.apply(this, args);
        };
    },
    wrapSetter(name, setter) {
        return function(value) {
            console.log("setting "+name, value);
            return setter.call(this, value);
        };
    }
};

const contextProto = CanvasRenderingContext2D.prototype;
const canvasProto = HTMLCanvasElement.prototype;
const getContext = canvasProto.getContext;
canvasProto.getContext = function(...args) {
    const ctx = getContext.apply(this, args);
    if (ctx && Object.getPrototypeOf(ctx) == contextProto)
        Object.setPrototypeOf(ctx, proxy);
    return ctx;
};

const proxy = new Proxy(contextProto, {
    get(target, name, receiver) {
         const value = Reflect.get(target, name, receiver);
         return typeof value == "function" ? hook.wrapMethod(name, value) : value;
    },
    set(target, name, value, receiver) {
        hook.wrapSetter(name, function(value) {
            Reflect.set(target, name, value, this);
        }).call(receiver, value);
    },
});


var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.font = "14px 'Arial'";
ctx.fillStyle = "red";
ctx.fillRect(130, 190, 40, 60);

这是相当低效的,因为方法和设置器在每次访问时都被包装,而不仅仅是一次。