3

オブジェクト内にカプセル化されたインタラクティブな read-eval-print-loop を (JavaScript で) 作成しました。しかし、私は最近、インタープリターに指定されたトップレベルの関数定義がインタープリターによって「記憶」されていないように見えることに気付きました。いくつかの診断作業の後、コアの問題を次のように減らしました。

   var evaler = {
     eval: function (str)
     {
       return eval(str);
     },
   };

   eval("function t1() { return 1; }");         // GOOD
   evaler.eval("function t2() { return 2; }");  // FAIL

この時点で、次の 2 つのステートメントが期待どおりに機能することを期待しています。

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はこの中間スコープで定義されており、すぐに破棄されます。@マット:あなたの「ハッキーな」アプローチは、これを解決する最良の方法かもしれません。

私はまだ根本的な原因についていくつかの作業を行っていますが、jdb で質の高い時間を過ごしたおかげで、何が起こっているのかをより理解できるようになりました。すでに説明したように、関数ステートメント likefunction t1() { return 42; }は 2 つのことを行います。

  • 式で得られるように、関数オブジェクトの匿名インスタンスを作成しますfunction() { return 42; }
  • その無名関数を現在の最上位のスコープに名前 でバインドしますt1

eval私の最初の質問は、オブジェクトのメソッド内から呼び出したときに、これらの 2 番目のことが発生しない理由についてです。

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"

これは、上でこれを書いたときに私が恐れていたことです。 ' のインスタンスであることが判明しましたNativeCall...関数が作成され、 内の変数にt2バインドされ、 への呼び出しが戻ると は消えます。t2NativeCallNativeCallevaler.eval

そして、これは物事が少しあいまいになるところです...私が望むほど多くの分析を行っていませんが、私の現在の作業理論は、NativeCallスコープが必要であるということです。(スタックフレームを少しバックアップすると、関数が「アクティブ化が必要」でゼロ以外の関数型を持つまでにスコープチェーンに追加されます。これらのことは単純な関数呼び出しにのみ当てはまると想定していますが、確かに知るのに十分な上流をたどっていない. 多分明日.)thisevalerevaler.evalNativeCallInterpreter.initFrame

4

5 に答える 5

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

編集

に応答して:

eval を使用する以外に、対話型の read-eval-print-loop を作成する別の方法はありますか?

あなたは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

したがって、評価するコードをファイルに書き込んでから、上記のコードを呼び出すことで、これをさらに一歩進めることができます...

はい、ばかげています。

于 2010-06-08T16:39:11.073 に答える
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のどこかに追加します。

于 2010-06-08T17:22:33.970 に答える
0

私はこの声明を考えます:

evaler.eval("function t2() { return 2; }");

は function を宣言せず、式の中で使用されているため、オブジェクトt2を返すだけです(それは ではなく、です)。Functionfunction declarationfunction 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
于 2010-06-08T16:30:28.760 に答える
0

Rhinoメーリングリストからこの回答を得ましたが、うまくいっているようです。

var window = this;

var evaler = {
    eval : function (str) {
         eval.call(window, str);
    }
};

重要なのは、call明示的に を設定することthisであり、これはt2適切な場所で定義されます。

于 2010-06-09T14:55:30.583 に答える
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ます。しかし、それは機能します。

于 2010-06-08T16:23:43.733 に答える