如何在 javascript 中将 Proxy 对象作为上下文传递给闭包

How to pass a Proxy object as context to a closure in javascript

提问人:Ross Snider 提问时间:8/21/2019 最后编辑:Ross Snider 更新时间:8/21/2019 访问量:1222

问:

注意:我现在相信这个问题是基于对 javascript 规范的假设,这些规范实际上是特定于实现的。

我正在尝试为复杂的动态 javascript 应用程序构建一个运行时调试钩子系统。一系列的选择让我可以选择使用 javascript Proxy 和 Reflect 元编程构造在我正在调试的应用程序中插入函数调用,将所有传入的函数参数包装在 Proxy/Reflect 构造中。

该方法涉及将高级应用程序函数替换为代理,并使用陷阱和处理程序来提供调试功能,最终以透明的方式将参数传递到应用程序。所有属性 get/set 和函数执行都照常执行。但是,通过将所有对象和函数包装在代理中,可以跟踪运行时。

我正在将这个钩子系统安装到 Chrome 中。

(注意:请不要提供建议使用不同方法来调试钩子的答案 - 选项已经过广泛评估。

问题在于应用程序中的某些 javascript 方法调用闭包并传递“this”参数。当“this”参数包装在代理中时,运行时无法执行闭包,而是引发“非法调用”异常。

我尝试重新设计调试钩子系统,以不包装某些方法的参数,或者有选择地包装参数。我无法找到一种方法来判断参数是否打算用作上下文,使尝试这种方法的代码硬编码为许多可能的方法和调用约定。归根结底,这太脆弱了,无法调用约定边缘案例,并且需要太多的案例陈述。

我还删除了在传递参数之前包装参数的逻辑。这消除了调试挂钩系统的好处,因此我总是还原逻辑以包装所有传入的参数。

alert.apply(this, [1]);

p = new Proxy(this, {});

try {
    alert.apply(p, [1]);
} catch (e) {
    console.log(e);
}

这将引发“非法调用”异常。

typeof this === 'object'
true

但似乎上下文就像其他一切一样是对象。

我希望将 Proxy() 传递到上下文应该会在调用中成功。除此之外,我希望上下文的类型足够具体,以确定它是否应该包装在 Proxy() 中。

我有两个问题。

(1) javascript 中上下文绑定闭包的语义是什么,会导致绑定到 Proxy(context) 失败并导致非法调用?

(2) 上下文是什么类型的对象,javascript 方法如何通过在运行时检查其属性来区分一个对象与其他 javascript 对象?

JavaScript 作用域 闭包 bind anonymous-function

评论

0赞 Bergi 8/21/2019
你现在的问题到底是什么?
0赞 Bergi 8/21/2019
"我还删除了在传递参数之前包装参数的逻辑。- 你能告诉我们这个逻辑吗?您也许可以扩展它,以便它在正确的位置展开代理。
0赞 Bergi 8/21/2019
我确实相信你已经彻底研究了其他方法,但我对此感到好奇。你能在某个地方分享你的评价吗?(评论,聊天,博客文章,...,虽然可能不是问题)
0赞 Ross Snider 8/21/2019
@Bergi 我有两个问题:(1) javascript 中上下文绑定闭包的语义是什么,会导致 Proxy(context) 因非法调用而失败?(2) 上下文是什么类型的对象,javascript 方法如何通过检查其属性来区分一个对象与其他 javascript 对象?注意:我尝试过通过在边缘情况下编码和一揽子/启发式策略在正确的位置解开代理。这两种方法都因自身原因而失败。我需要 (1) 和 (2) 的答案才能正式正确地进行。
1赞 Paul 8/21/2019
出于同样的原因抛出错误。您可以创建一个行为与警报相同的函数,如下所示:将抛出错误,但不会。new Proxy( x, {} ) !== xfunction foo ( ) { if ( this !== window ) throw new TypeError( 'Illegal Invocation' ); }foo.apply( new Proxy( window, { } ) );foo.apply( window )

答:

1赞 Bergi 8/21/2019 #1

上下文是什么类型的对象,javascript 方法如何通过在运行时检查其属性来区分一个对象与其他 javascript 对象?

没有特殊类型。每个对象都可以通过调用方法成为上下文。大多数将成为方法调用上下文的对象确实将该方法作为(继承的)属性,但不能保证。

你无法区分它们。

javascript 中上下文绑定的语义是什么,会导致绑定到 Proxy(context) 失败并导致非法调用?

当方法是本机方法时。在用户代码函数中,作为代理的上下文并没有什么区别,当您访问它时,它只会充当代理。this

问题在于本机方法,它们期望其参数是相应类型的本机对象。当然,这些对象仍然是 javascript 对象,但它们也可能包含内部属性上的私有数据。例如,代理的目标和处理程序引用也是通过此类内部属性实现的 - 有时可以在调试器中检查它们。本机方法不知道解开代理并使用其目标,它们只是查看对象并注意到它没有方法完成其工作所需的内部属性。你也可以经过一片平原。this{}

此类方法的示例可以在 ECMAScript 运行时的内置中找到:

但也(甚至更多)作为环境提供的主机对象:

  • window.alert/prompt
  • EventTarget.prototype.addEventListener/removeEventListener
  • document.createElement
  • Element.prototype.appendChild/remove/
  • 实际上只是特定于浏览器的任何内容
  • 但也在其他环境中,如 NodeJS OS 模块

我尝试通过在边缘情况下编码和一揽子/启发式策略在正确的位置解开代理。

我认为唯一合理的方法是检查被调用的函数是否是本机函数,并为它们解开所有参数(包括参数)。this

只有少数本机函数可以被列入白名单,例如语言标准中明确指定用于处理任意对象的大多数本机函数。Array.prototype

评论

0赞 Ross Snider 8/21/2019
是否可以以“伪装”为浏览器本机代码的假设/检查的内部属性的方式设置对象的属性?我假设没有 - 这将被视为安全问题?
0赞 Bergi 8/21/2019
@RossSnider 不,这是不可能的。这就是为什么它们被称为“内部”的原因。它们根本不是可以使用点或括号语法访问的属性。是的,访问这些内部属性也是一个安全问题。
0赞 Ross Snider 8/21/2019
这非常有帮助,我认为可以充分回答这两个问题。在接受它之前,我会在 SE 上给它一些时间,以防发布其他信息。