为什么这个 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);
这是相当低效的,因为方法和设置器在每次访问时都被包装,而不仅仅是一次。
我确定我创建代理的方式是正确的,因为当我将 属性 登录到 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);
这是相当低效的,因为方法和设置器在每次访问时都被包装,而不仅仅是一次。