问题描述
我确定我创建代理的方式是正确的,因为当我将属性登录到 devtools 控制台时,我可以看到设置器已被代理。
我主要是按照这个 Question 的回答来构建这个的。尽管如此,我必须说这个答案看起来很复杂,所以我从中剥离了很多东西,以采用简单的风格,您可以从下面的代码中看到。
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",target,args)
return target.apply(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,value);
console.log("set",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"));
解决方法
不要将 getter/setter 与代理混用。使用其中之一,不要同时使用。
这是一个仅使用属性描述符的解决方案:
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,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,receiver) {
const value = Reflect.get(target,receiver);
return typeof value == "function" ? hook.wrapMethod(name,value) : value;
},set(target,value,receiver) {
hook.wrapSetter(name,function(value) {
Reflect.set(target,this);
}).call(receiver,value);
},});
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.font = "14px 'Arial'";
ctx.fillStyle = "red";
ctx.fillRect(130,60);
这是相当低效的,因为方法和设置器在每次访问时都被包装,而不仅仅是一次。