我已经(在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
没有定义。从evaler
内调用eval的行为与全局定义未被记录的顶级调用充分不同。发生什么事就是打电话给
evaler.eval
返回一个函数对象,所以我假设t2
被定义并存储在我无法访问的其他一组绑定中。 (它未被定义为evaler
中的成员。)
这有什么简单的解决方法吗?我已经尝试了各种各样的修复,并没有偶然发现一个有效的修复程序。 (我所做的大部分工作集中在将eval调用匿名函数,改变调用方式,chainging __parent__
等)。
有关如何解决此问题的任何想法?
以下是更多调查的结果:
tl; dr:Rhino在实例上调用方法时为范围链添加了一个中间范围。正在此中间范围中定义t2
,该范围立即被丢弃。 @Matt:你的'hacky'方法可能是解决这个问题的最佳方法。
我仍在为根本原因做一些工作,但是由于jdb的一些高质量时间,我现在对正在发生的事情有了更多的了解。正如已经讨论过的,像function 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"
这或多或少是我在上面写这篇文章时所害怕的:“在直接评估的情况下,t2在全球环境中受到约束。在evaler案例中,它被”绑定在其他地方“在这种情况下,'其他'原来是NativeCall
的实例... t2
函数被创建,绑定到t2
中的NativeCall
变量,NativeCall
当evaler.eval
的调用返回时,1}}消失。
这就是事情变得有点模糊......我没有做过我想做的那么多分析,但我目前的工作理论是需要NativeCall
范围来确保this
在evaler
的调用中执行时,1}}指向evaler.eval
。 (稍微备份堆栈帧,当函数'需要激活'并且函数类型为非零时,NativeCall
会被Interpreter.initFrame
添加到作用域链中。我假设这些东西仅适用于简单的函数调用,但没有足够的上游跟踪以确定。也许明天。)
答案 0 :(得分:3)
您的代码实际上并没有失败。 eval
正在返回您永远不会调用的function
。
print(evaler.eval("function t2() { return 2; }")()); // prints 2
要拼出一点:
x = evaler.eval("function t2() { return 2; }"); // this returns a function
y = x(); // this invokes it, and saves the return value
print(y); // this prints the result
修改强>
回应:
是否有另一种方法来创建交互式read-eval-print-loop而不是使用eval?
因为你正在使用Rhino ..我猜你可以用一个java Process对象来调用Rhino来读取一个带有js的文件吗?
假设我有这个文件:
<强> test.js 强>
function tf2() {
return 2;
}
print(tf2());
然后我可以运行这段代码,它调用Rhino来评估该文件:
process = java.lang.Runtime.getRuntime().exec('java -jar js.jar test.js');
result = java.io.BufferedReader(java.io.InputStreamReader(process.getInputStream()));
print(result.readLine()); // prints 2, believe it or not
所以你可以通过将一些代码写入eval到文件来更进一步,然后调用上面的代码......
是的,这太荒谬了。
答案 1 :(得分:1)
您遇到的问题是JavaScript使用功能级别范围。
当您从已定义的eval()
函数中调用eval
时,可能会在t2()
函数的范围内创建函数eval: function(str) {}
。
您可以使用evaler.eval('global.t2 = function() { return 2; }'); t2();
你也可以这样做:
t2 = evaler.eval("function t2() { return 2; }");
t2();
或者....
var someFunc = evaler.eval("function t2() { return 2; }");
// if we got a "named" function, lets drop it into our namespace:
if (someFunc.name) this[someFunc.name] = someFunc;
// now lets try calling it?
t2();
// returns 2
更进一步:
var evaler = (function(global){
return {
eval: function (str)
{
var ret = eval(str);
if (ret.name) global[ret.name] = ret;
return ret;
}
};
})(this);
evaler.eval('function t2() { return 2; }');
t2(); // returns 2
使用DOM,您可以通过注入“根级”脚本代码而不是使用eval()
来解决此功能级别的范围问题。您将创建一个<script>
标记,将其文本设置为您要评估的代码,并将其附加到DOM的某个位置。
答案 2 :(得分:0)
您的函数名称“eval”是否可能与eval函数本身发生冲突?试试这个:
var evaler = {
evalit: function (str)
{
return window.eval(str);
},
};
eval("function t1() { return 1; }");
evaler.evalit("function t2() { return 2; }");
修改强>
我修改了使用@Matt的建议并进行了测试。这按预期工作。
好吗?我个人对eval
皱眉。但它的确有效。
答案 3 :(得分:0)
我认为这句话:
evaler.eval("function t2() { return 2; }");
不会声明函数t2
,它只返回Function
对象(它不是function declaration
,而是function operator
),因为它在表达式中使用。
当评估在函数内部发生时,新创建的函数的范围仅限于evaler.eval
范围(即,您只能使用t2
函数中的evaler.eval
函数:
js> function foo () {
eval ("function baz() { return 'baz'; }");
print (baz);
}
js> foo ();
function baz() {
return "baz";
}
js> print(baz);
typein:36: ReferenceError: baz is not defined
答案 4 :(得分:0)
我从Rhino邮件列表中得到了这个答案,它似乎有效。
var window = this;
var evaler = {
eval : function (str) {
eval.call(window, str);
}
};
关键是call
明确设置this
,并在适当的位置定义t2
。