gpt4 book ai didi

javascript - 是否可以将 Proxy 与 native 浏览器对象(HTMLElement、Canvas2DRenderingContext 等)一起使用?

转载 作者:数据小太阳 更新时间:2023-10-29 03:55:26 24 4
gpt4 key购买 nike

我试图完成的事情。我想共享一个 Canvas (因为我正在做的事情很重)所以我想我会做一个有限的资源管理器。您会通过 promise 向它请求资源,在本例中为 Canvas2DRenderingContext 。它将上下文包装在一个可撤销的代理中。完成后,您需要调用 release,这既会将 Canvas 返回给有限的资源管理器,这样它就可以将它交给其他人,并且它会撤销代理,这样用户就不会意外地再次使用该资源。

除非我制作 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) 它肯定会失败但考虑到上面的简单代码我希望它实际上不可能有意义地包装任何 DOM 元素或其他 native 对象。

下面是代理与普通 JS 对象一起工作的证明。他们基于类工作(因为函数在原型(prototype)链上)。而且它们不适用于 native 对象。

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,我选择的解决方案是将 Canvas 包装在一个小类中,该小类可以完成我使用它的目的。优点是它更容易测试(因为我可以传入模拟)并且我可以毫无问题地代理该对象。不过还是想知道

  1. 为什么代理不适用于 native 对象?
  2. Proxy 不适用于 native 对象的任何原因是否适用于使用 JavaScript 对象的情况?
  3. 是否有可能让 Proxy 与 native 对象一起工作。

最佳答案

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); }

类似的东西应该可以满足您的期望。
一个可撤销的代理,带有获取和设置陷阱的处理程序,用于上下文。

请记住,当 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

编辑

正如下面的评论所指出的,我完全忽略了测试宿主对象上的方法调用,事实证明,这些对象都是 protected 。这归结为宿主对象的怪异,它们按照自己的规则进行游戏。

使用上述代理,proxy.drawImage.apply(ctx, args) 就可以正常工作。然而,这是违反直觉的。

我在这里假设失败的情况是 Canvas、Image、Audio、Video、Promise(例如基于实例的方法)等。我还没有讨论这部分 Proxies 的规范,也没有讨论这是属性描述符还是主机绑定(bind),但我假设是后者,如果不是两者都是的话。

也就是说,您应该能够通过以下更改覆盖它:

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 的人使用。

关于javascript - 是否可以将 Proxy 与 native 浏览器对象(HTMLElement、Canvas2DRenderingContext 等)一起使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48535551/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com