我已经(在 JavaScript 中)编写了一个封装在对象中的交互式 read-eval-print-loop。但是,我最近注意到为解释器指定的顶级函数定义似乎没有被解释器“记住”。经过一些诊断工作,我将核心问题简化为:
var evaler = {
eval: function (str)
{
return eval(str);
},
};
eval("function t1() { return 1; }"); // GOOD
evaler.eval("function t2() { return 2; }"); // FAIL
在这一点上,我希望以下两个语句能按预期工作:
print(t1()); // => Results in 1 (This works)
print(t2()); // => Results in 2 (this fails with an error that t2 is undefined.)
相反,我得到的是该行的预期值t1
,并且该t2
行失败并出现未绑定的错误t2
。
IOW:运行此脚本后,我有一个定义t1
,而没有定义t2
。从内部调用 eval 的行为evaler
与顶层调用有很大不同,因此全局定义不会被记录下来。确实发生的是调用
evaler.eval
返回一个函数对象,所以我假设它t2
被定义并存储在我无权访问的其他一些绑定集中。(它没有被定义为 中的成员evaler
。)
有什么简单的解决方法吗?我已经尝试了各种修复方法,并没有偶然发现一种有效的方法。(我所做的大部分工作都集中在将对 eval 的调用放在匿名函数中,并改变调用方式、链接__parent__
等)
关于如何解决这个问题的任何想法?
这是进一步调查的结果:
tl;dr:Rhino 在调用实例上的方法时会在作用域链中添加一个中间作用域。t2
被定义在这个中间范围内,立即被丢弃。@Matt:您的“hacky”方法很可能是解决此问题的最佳方法。
我仍在为根本原因做一些工作,但是由于在 jdb 上度过了一段美好的时光,我现在对正在发生的事情有了更多的了解。如前所述,函数语句 likefunction t1() { return 42; }
做了两件事。
- 它创建一个函数对象的匿名实例,就像您使用表达式一样
function() { return 42; }
- 它将匿名函数绑定到当前的顶级作用域,名称为
t1
。
eval
我最初的问题是,当我从对象的方法中调用时,为什么我没有看到第二件事发生。
在 Rhino 中实际执行绑定的代码似乎在函数中org.mozilla.javascript.ScriptRuntime.initFunction
。
if (type == FunctionNode.FUNCTION_STATEMENT) {
....
scope.put(name, scope, function);
对于上述t1
情况,scope
这是我设置的顶级范围。这是我想要定义我的顶级函数的地方,所以这是一个预期的结果:
main[1] print function.getFunctionName()
function.getFunctionName() = "t1"
main[1] print scope
scope = "com.me.testprogram.Main@24148662"
但是,在这种t2
情况下,scope
完全是另一回事:
main[1] print function.getFunctionName()
function.getFunctionName() = "t2"
main[1] print scope
scope = "org.mozilla.javascript.NativeCall@23abcc03"
NativeCall
这是我预期的顶级范围的父范围:
main[1] print scope.getParentScope()
scope.getParentScope() = "com.me.testprogram.Main@24148662"
这或多或少是我在上面写这个时所害怕的:“在直接 eval 的情况下,t2 被绑定在全局环境中。在 evaler 的情况下,它被绑定在 'elsewhere'” 在这种情况下,'elsewhere ' 结果是NativeCall
... 的实例,该t2
函数被创建,绑定到 中的一个t2
变量NativeCall
,并且当调用返回NativeCall
时消失。evaler.eval
这就是事情变得有点模糊的地方......我没有做尽可能多的分析,但我目前的工作理论是需要范围来NativeCall
确保在调用. (稍微备份堆栈帧,当函数“需要激活”并且具有非零函数类型时,它们被添加到作用域链中。我假设这些事情仅适用于简单的函数调用,但没有'没有追踪到足够确定的上游。也许明天。)this
evaler
evaler.eval
NativeCall
Interpreter.initFrame