提问人:Ross Snider 提问时间:8/21/2019 最后编辑:Ross Snider 更新时间:8/21/2019 访问量:1222
如何在 javascript 中将 Proxy 对象作为上下文传递给闭包
How to pass a Proxy object as context to a closure in javascript
问:
注意:我现在相信这个问题是基于对 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 方法如何通过在运行时检查其属性来区分一个对象与其他 javascript 对象?
没有特殊类型。每个对象都可以通过调用方法成为上下文。大多数将成为方法调用上下文的对象确实将该方法作为(继承的)属性,但不能保证。
你无法区分它们。
javascript 中上下文绑定的语义是什么,会导致绑定到 Proxy(context) 失败并导致非法调用?
当方法是本机方法时。在用户代码函数中,作为代理的上下文并没有什么区别,当您访问它时,它只会充当代理。this
问题在于本机方法,它们期望其参数是相应类型的本机对象。当然,这些对象仍然是 javascript 对象,但它们也可能包含内部属性上的私有数据。例如,代理的目标和处理程序引用也是通过此类内部属性实现的 - 有时可以在调试器中检查它们。本机方法不知道解开代理并使用其目标,它们只是查看对象并注意到它没有方法完成其工作所需的内部属性。你也可以经过一片平原。this
{}
此类方法的示例可以在 ECMAScript 运行时的内置中找到:
Map.prototype.has
/get
/set
/...
设置.prototype.has
/get
/set
/...
TypeArrayPrototype.slice
/copyWithin
/map
/forEach
/...
Number
/String
/Boolean
原型方法
但也(甚至更多)作为环境提供的主机对象:
window.alert
/prompt
EventTarget.prototype.addEventListener
/removeEventListener
document.createElement
Element.prototype.appendChild
/remove
/…
- 实际上只是特定于浏览器的任何内容
- 但也在其他环境中,如 NodeJS
OS
模块
我尝试通过在边缘情况下编码和一揽子/启发式策略在正确的位置解开代理。
我认为唯一合理的方法是检查被调用的函数是否是本机函数,并为它们解开所有参数(包括参数)。this
只有少数本机函数可以被列入白名单,例如语言标准中明确指定用于处理任意对象的大多数本机函数。Array.prototype
评论
new Proxy( x, {} ) !== x
function foo ( ) { if ( this !== window ) throw new TypeError( 'Illegal Invocation' ); }
foo.apply( new Proxy( window, { } ) );
foo.apply( window )