草庐IT

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

coder 2024-07-16 原文

我试图完成的事情。我想共享一个 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/

有关javascript - 是否可以将 Proxy 与 native 浏览器对象(HTMLElement、Canvas2DRenderingContext 等)一起使用?的更多相关文章

  1. ruby - 在 Ruby 中用键盘诅咒数组浏览 - 2

    我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作

  2. ruby-on-rails - 错误 : Error installing pg: ERROR: Failed to build gem native extension - 2

    我克隆了一个rails仓库,我现在正尝试捆绑安装背景:OSXElCapitanruby2.2.3p173(2015-08-18修订版51636)[x86_64-darwin15]rails-v在您的Gemfile中列出的或native可用的任何gem源中找不到gem'pg(>=0)ruby​​'。运行bundleinstall以安装缺少的gem。bundleinstallFetchinggemmetadatafromhttps://rubygems.org/............Fetchingversionmetadatafromhttps://rubygems.org/...Fe

  3. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  4. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  5. ruby-on-rails - gem install rmagick -v 2.13.1 错误 Failed to build gem native extension on Mac OS 10.9.1 - 2

    我已经通过提供MagickWand.h的路径尝试了一切,我安装了命令工具。谁能帮帮我?$geminstallrmagick-v2.13.1Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingrmagick:ERROR:Failedtobuildgemnativeextension./Users/ghazanfarali/.rvm/rubies/ruby-1.8.7-p357/bin/rubyextconf.rbcheckingforRubyversion>=1.8.5...yescheckingfor/

  6. ruby - Ruby 1.9.1 中的 native 线程,对我有什么好处? - 2

    所以,Ruby1.9.1现在是declaredstable.Rails应该与它一起工作,并且正在慢慢地将gem移植到它。它具有native线程和全局解释器锁(GIL)。自从GIL到位后,原生线程是否比1.9.1中的绿色线程有任何优势? 最佳答案 1.9中的线程是原生的,但它们被“放慢了速度”,一次只允许一个线程运行。这是因为如果线程真的并行运行,它会混淆现有代码。优点:IO现在在线程中是异步的。如果一个线程阻塞在IO上,那么另一个线程将继续执行直到IO完成。C扩展可以使用真正的线程。缺点:任何非线程安全的C扩展都可能存在使用Thre

  7. ruby - 强制浏览器下载文件而不是打开文件 - 2

    我要下载http://foobar.com/song.mp3作为song.mp3,而不是让Chrome在其native中打开它浏览器中的播放器。我怎样才能做到这一点? 最佳答案 您只需要确保发送这些header:Content-Disposition:attachment;filename=song.mp3;Content-Type:application/octet-streamContent-Transfer-Encoding:binarysend_file方法为您完成:get'/:file'do|file|file=File.

  8. ruby - 404 未找到,但可以从网络浏览器正常访问 - 2

    我在这方面尝试了很多URL,在我遇到这个特定的之前,它们似乎都很好:require'rubygems'require'nokogiri'require'open-uri'doc=Nokogiri::HTML(open("http://www.moxyst.com/fashion/men-clothing/underwear.html"))putsdoc这是结果:/Users/macbookair/.rvm/rubies/ruby-2.0.0-p481/lib/ruby/2.0.0/open-uri.rb:353:in`open_http':404NotFound(OpenURI::HT

  9. ruby - 如何在 watir 测试套件结束时关闭浏览器? - 2

    使用ruby​​的watir测试网络应用程序时,浏览器最后会保持打开状态。网上的一些建议是,要进行真正的单元测试,您应该在每次测试时(在拆卸调用中)打开和关闭浏览器,但这很慢而且毫无意义。或者他们做这样的事情:defself.suites=superdefs.afterClass#Closebrowserenddefs.run(*args)superafterClassendsend但这会导致摘要输出不再显示(诸如“100次测试、100次断言、0次失败、0次错误”之类的内容仍应显示)。我怎样才能让ruby​​或watir在我的测试结束时关闭浏览器? 最佳答案

  10. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

随机推荐