在从对象内部调用时,使用'eval'来定义顶层函数时遇到问题

时间:2010-06-08 16:19:51

标签: javascript rhino

我已经(在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变量,NativeCallevaler.eval的调用返回时,1}}消失。

这就是事情变得有点模糊......我没有做过我想做的那么多分析,但我目前的工作理论是需要NativeCall范围来确保thisevaler的调用中执行时,1}}指向evaler.eval。 (稍微备份堆栈帧,当函数'需要激活'并且函数类型为非零时,NativeCall会被Interpreter.initFrame添加到作用域链中。我假设这些东西仅适用于简单的函数调用,但没有足够的上游跟踪以确定。也许明天。)

5 个答案:

答案 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