Javascript 是否可以将代理与本机浏览器对象(HTMLElement、Canvas2RenderingContext等)一起使用?
我想要达到的目标。我想共享一个画布(因为我正在做的东西很重),所以我想我应该做一个有限的资源管理器。您可以通过promise请求它提供资源,在本例中是一个Javascript 是否可以将代理与本机浏览器对象(HTMLElement、Canvas2RenderingContext等)一起使用?,javascript,Javascript,我想要达到的目标。我想共享一个画布(因为我正在做的东西很重),所以我想我应该做一个有限的资源管理器。您可以通过promise请求它提供资源,在本例中是一个Canvas2DRenderingContext。它将把上下文包装在一个可撤销的代理中。完成后,您需要调用release,它将画布返回给有限的资源管理器,以便将画布提供给其他人,并撤销代理,以便用户不会再次意外使用该资源 除非我为Canvas2DRenderingContext创建代理,否则它会失败 const ctx=document.cr
Canvas2DRenderingContext
。它将把上下文包装在一个可撤销的代理中。完成后,您需要调用release,它将画布返回给有限的资源管理器,以便将画布提供给其他人,并撤销代理,以便用户不会再次意外使用该资源
除非我为Canvas2DRenderingContext
创建代理,否则它会失败
const ctx=document.createElement('canvas').getContext('2d');
constproxy=新代理(ctx,{});
//尝试通过代理更改画布的宽度
测试(()=>{proxy.canvas.width=100;});//错误
//尝试通过代理来翻译源代码
test(()=>{proxy.translate(1,2);});//错误
功能测试(fn){
试一试{
fn();
}捕获(e){
日志(“失败:”,e,fn);
}
}
像这样的东西应该能满足你的期望。上下文的可撤销代理,带有get和set陷阱的处理程序 请记住,当
Proxy.revocable()
的实例被撤销时,该代理的任何后续访问都将抛出,因此现在需要使用try/catch,以防它确实被撤销
只是为了好玩,这里介绍了如何在不担心抛出的情况下做完全相同的事情(就简单地使用访问器而言;不保证在您仍然有访问权限的情况下做错事):
编辑
正如在下面的评论中指出的,我完全忽略了测试宿主对象上的方法调用,结果证明,这些方法调用都是受保护的。这可以归结为宿主对象的怪异,它们按照自己的规则进行游戏
使用如上所述的代理,proxy.drawImage.apply(ctx,args)
可以正常工作。
然而,这是违反直觉的
我假设在这里失败的案例有画布、图像、音频、视频、Promise(例如基于实例的方法)等等。我还没有就代理的这一部分与规范进行讨论,也没有讨论这是属性描述符还是主机绑定,但我将假设它是后者,如果不是两者都是的话
也就是说,您应该能够通过以下更改覆盖它:
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返回一个函数,该函数保持与原始上下文的关系。代理通常处理这个常见的JS问题
……这引起了其自身的安全担忧;现在,有人可以缓存该值,并永久访问,比如说,drawImage
,方法是
const draw=proxy.drawImage代码>。。。
再说一次,他们已经有能力保存真实的渲染上下文,只需说
const ctx=proxy.canvas.getContext(“2d”)代码>
…所以我在这里假设了某种程度的诚意
对于一个更安全的解决方案,还有其他的修复方法,尽管使用canvas,除非它只在内存中,否则上下文最终将对任何能够阅读DOM的人可用。我认为关于原始代码抛出原因的补充说明将是这个答案的+答案。为什么一些属性不能被no-op处理程序转发{}
?@kaido编辑。不管怎样,你都不会发现无操作处理程序的任何好处;没有处理程序来获取。另一方面,代理模仿受保护对象;如果不能像那个对象那样(通过处理程序),您将遇到问题。对不起,我对代理这件事很陌生,请注意,我不是询问者。我只是注意到,虽然你的答案确实为问题提供了解决方案,但它并不能解释问题。我仍然看不到您的答案中有任何内容说明为什么在这些情况下会抛出no-op处理程序op。虽然我可以看到发生了什么,但我无法用清晰的语言向自己解释。同时传递Reflect
对象将被视为“无操作”处理程序。@gman因此,这方面的一个主要问题是,您现在正面临重大的状态突变问题。缓存两个函数引用(想想.bind
),然后调用第一个函数。运行哪种方法?现在,如果团队中没有人或使用代码的人将绑定方法作为回调传入,那么';这是完全正确的。但是,如果这是库代码,那么为了保存函数上下文,您会打断很多人。再说一次,如果我们';你说的是以每秒60帧的速度打几百次电话,我明白了。以10fps的速度,我个人不这么认为。不幸的是,似乎无论您作为处理程序传递什么,都将从代理本身而不是从处理程序访问目标属性。覆盖CanvasRenderingContext2D.prototype.canvas以在getter中记录此
,从而记录代理实例。因此,即使是新代理(ctx,ctx)
也不能作为禁止使用的代理。。。
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
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;
}
});