我想要完成的事情。我想分享一个画布(因为我正在做的事情非常沉重)所以我认为我做了一个有限的资源管理器。您可以通过承诺询问资源,在这种情况下为Canvas2DRenderingContext
。它会将上下文包装在可撤销的代理中。当您完成后,您需要调用release,它将画布返回给有限的资源管理器,以便它可以将其提供给其他人并且它撤销代理,以便用户不会再次意外地使用该资源。
除非我代理Canvas2DRenderingContext
,否则它失败。
const ctx = document.createElement('canvas').getContext('2d');
const proxy = new Proxy(ctx, {});
// try to change the width of the canvas via the proxy
test(() => { proxy.canvas.width = 100; }); // ERROR
// try to translate the origin of via the proxy
test(() => { proxy.translate(1, 2); }); // ERROR
function test(fn) {
try {
fn();
} catch (e) {
console.log("FAILED:", e, fn);
}
}

上面的代码在Chrome中生成Uncaught TypeError: Illegal invocation
,在Firefox中生成TypeError: 'get canvas' called on an object that does not implement interface CanvasRenderingContext2D.
这是Proxy的预期限制还是错误?
注意:当然还有其他解决方案。我可以删除代理,只是不用担心。我也可以将canvas包装在一些JavaScript对象中,该对象只暴露我需要的函数并代理它。如果这应该有效,我会更好奇。 This Mozilla blog post kind of indirectly suggests it's supposed to be possbile因为它实际上提到使用带有HTMLElement
的代理,如果只是指出如果你调用someElement.appendChild(proxiedElement)
它肯定会失败但是考虑到上面的简单代码我会期待它&# 39;实际上不可能有意义地包装任何DOM元素或其他本机对象。
以下是Proxies使用普通JS对象的证明。它们与基于类的工作(如函数在原型链上)。而且他们不会使用原生物体。
const img = document.createElement('img')
const proxy = new Proxy(img, {});
console.log(proxy.src);

也会因同样的错误而失败。他们没有使用JavaScript对象的地方
function testNoOpProxy(obj, msg) {
log(msg, '------');
const proxy = new Proxy(obj, {});
check("get property:", () => proxy.width);
check("set property:", () => proxy.width = 456);
check("get property:", () => proxy.width);
check("call fn on object:", () => proxy.getContext('2d'));
}
function check(msg, fn) {
let success = true;
let r;
try {
r = fn();
} catch (e) {
success = false;
}
log(' ', success ? "pass" : "FAIL", msg, r, fn);
}
const test = {
width: 123,
getContext: function() {
return "test";
},
};
class Test {
constructor() {
this.width = 123;
}
getContext() {
return `Test width = ${this.width}`;
}
}
const testInst = new Test();
const canvas = document.createElement('canvas');
testNoOpProxy(test, 'plain object');
testNoOpProxy(testInst, 'class object');
testNoOpProxy(canvas, 'native object');
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}

pre { margin: 0; }

FWIW我选择的解决方案是将画布包装在一个小类中,它可以完成我正在使用它的东西。优点是它更容易测试(因为我可以通过模拟),我可以代理该对象没问题。不过,我想知道
答案 0 :(得分:3)
const handlers = {
get: (target, key) => key in target ? target[key] : undefined,
set: (target, key, value) => {
if (key in target) {
target[key] = value;
}
return value;
}
};
const { revoke, proxy } = Proxy.revocable(ctx, handlers);
// elsewhere
try {
proxy.canvas.width = 500;
} catch (e) { console.log("Access has been revoked", e); }
这样的事情应该做你期望的事 一个可撤销的代理,带有for和set陷阱的处理程序,用于上下文。
请记住,当Proxy.revocable()
的实例被撤销时,该代理的任何后续访问都将抛出,因此现在所有内容都需要使用try / catch,如果它确实已经被撤销。
只是为了好玩,这里有你怎么做同样的事情而不用担心扔掉(只是简单地使用访问者;不保证在你仍然有权访问时做错事):
const RevocableAccess = (item, revoked = false) => ({
access: f => revoked ? undefined : f(item),
revoke: () => { revoked = true; }
});
const { revoke, access: useContext } = RevocableAccess(ctx);
useContext(ctx => ctx.canvas.width = 500);
revoke();
useContext(ctx => ctx.canvas.width = 200); // never fires
正如下面的评论所指出的那样,我完全忽略了对主机对象上的方法调用进行测试,结果证明它们都受到保护。这归结为主机对象中的怪异,它们可以按照自己的规则发挥作用。
使用上面的代理,proxy.drawImage.apply(ctx, args)
可以正常工作。
然而,这是违反直觉的。
我在这里假设失败的案例是Canvas,Image,Audio,Video,Promise(例如基于方法)等。我还没有对Proxies的这一部分的规范做出贡献,无论这是一个属性描述符,还是一个主机绑定的东西,但我会假设它是后者,如果不是两个。
那就是说,你应能够通过以下更改覆盖它:
const { proxy, revoke } = Proxy.revocable(ctx, {
get(object, key) {
if (!(key in object)) {
return undefined;
}
const value = object[key];
return typeof value === "function"
? (...args) => value.apply(object, args)
: value;
}
});
在这里,我仍然"得到"关闭原始对象的方法,来调用它。
碰巧在值是函数的情况下,我调用bind来返回一个维护与原始上下文的this
关系的函数。代理通常会处理这个常见的JS问题。
......这引起了自身的安全问题;有人可以现在缓存价值,并且可以永久访问drawImage
,通过说
const draw = proxy.drawImage;
...
然后,他们已经有能力保存真实的渲染上下文,只是说
const ctx = proxy.canvas.getContext("2d");
......所以我在这里假设某种程度的善意。
对于更安全的解决方案,还有其他一些修复,但是使用canvas,除非它只在内存中,所以任何能够读取DOM的人都可以使用上下文。