3

Javascript で Read-Eval-Print-Loop を一緒にハックしようとしています。(これは Web ベースの「Javascript を教える」プラットフォーム用です。) ほとんど動作するものがありますが、閉鎖に関する奇妙なバグに遭遇しています。

これは、ループのコアの簡略化されたバージョンです。で作成された状態を維持するためにクロージャーと継続を使用したいので、このように書きましたeval

// listing 1

var repl = function(result) {
  return {
    result:
      result,
    then:
      function (expression, continuation) {
        return continuation(eval(expression));
      },
  };
}

そして、それはほとんど機能します。たとえば、式var x = 1x++、のシーケンスを正しく評価しxます。

// listing 2

repl(eval('var x = 1')).then('x++', repl)
                       .then('x', repl)
                       .result

// evaluates to 2

したがって、式は、グローバル スコープを汚染することなく、以前に宣言されたローカル変数にアクセスして変更できます。これは素晴らしいことです。 しかし、変数宣言 (つまりvar ...) は、チェーンの最初の式に対してのみ機能します。 たとえば、一連の式var x = 1, var y = 2,はy is not definedエラーをyスローします。

// listing 3

repl(eval('var x = 1')).then('var y = 2', repl)
                       .then('y', repl)
                       .result;

// throws "y is not defined"

repl次のように、各インスタンスをその定義に置き換えると、このエラーを回避できることがわかりました。

//listing 4
//
// same as listing 3, but repl is replaced with its
// definition from listing 1

function (result) {
    return {
      result:
        result,
      then:
        function (expression, continuation) {
          return continuation(eval(expression));
        }
    },
  })(eval('var x = 1')).then(
    'var y = 2',
    function (result) {
      return {
        result:
          result,
        then:
          function (expression, continuation) {
            return continuation(eval(expression));
          },
      };
    }
  ).then(
    'y',
    function (result) {
      return {
        result:
          result,
        then:
          function (expression, continuation) {
            return continuation(eval(expression));
          },
      };
    }
  ).result

// evaluates to 2

これは 2 に評価されます。したがって、各反復evalの定義を ing するだけで問題を解決できると思います。replしかし、確かにもっと良い解決策があります...そうではありませんか?


編集:の各インスタンスを に置き換えてみましrepleval('('+repl+')')が、問題は解決しませんでした。私は何が欠けていますか?

4

2 に答える 2

2

RobG は既に問題の原因を説明しているので、この回答は考えられる回避策に限定します。

グローバル スコープを汚染することを気にしない場合 (おそらく、ここでもそれが望まれますか?)、間接eval呼び出しを使用して、すべての式を強制的にグローバルに評価することができます。

var repl = function(result) {
  return {
    result:
      result,
    then:
      function (expression, continuation) {
        return continuation((1,eval)(expression));
      },
  };
}

http://jsfiddle.net/y37Pj/

継続の代わりに呼び出すこともできるため、毎回repl渡す必要はありません。then

var repl = function(result) {
  return {
    result:
      result,
    then:
      function (expression) {
        return repl((1,eval)(expression));
      },
  };
}

repl(eval('var x = 1')).then('var y = 2')
                       .then('y')
                       .result

http://jsfiddle.net/y37Pj/1/


ピタロウが受け入れた解決策は、この回答へのコメントの jsFiddle に隠されていました。参考までに、「醜いハック」ソリューションのわずかに変更されたバージョンを次に示します (関数のソース コードを評価してクロージャーを作成します)。

function make_repl() {
  return {
    result: undefined,
    then: function (expression) {
      return {
        result: eval(expression),
        then: eval('('+this.then.toString()+')'),
      };
    },
  };
};

var repl = make_repl();

repl = repl.then('var x = 1').then('var y = 2').then('x + " " + y');

console.log(repl.result); // prints "1 2"

console.log(typeof(x), typeof(y));
// prints "undefined undefined", so we know the global scope was not touched
于 2013-10-17T01:58:10.087 に答える
2

evalによって作成された変数は、呼び出し実行コンテキストで作成されます。したがって、同じスコープ チェーンで後で作成された他の実行コンテキストでのみ使用できます。

もしあなたがそうするなら:

eval( 'var x = 3' );

グローバル コードとして、式が評価されるときに、値3を持つ変数xがグローバル コンテキスト内の変数として作成されます。evalが関数コンテキストから呼び出された場合、すべての変数宣言はそのコンテキストに対してローカルです。

function doEval()
  eval( 'var x = 3' );
}

doEval()呼び出されると、関数の実行コンテキストでxが作成され、関数内でのみ使用できるようになります。

例えば:

function doEval() {

  // Create local variable x
  eval( 'var x = 3' );

  // x is available on this function's scope chain
  var foo = function(){alert(x)};

  foo();
}

doEval()   // shows 3

alert(x) // Refernce error, x is not defined

コードでは、関数呼び出しごとに、前のものにアクセスできない新しい実行コンテキストと変数環境が作成されます。ローカル コンテキストまたは変数オブジェクトへの参照を取得できないため、それを他の関数に渡すことはできません。

複数の文字列をコードとして評価するには、次のように順次呼び出してevalを使用できます。

function evaluateExpressions() {
  for (var i=0, iLen=arguments.length; i<iLen; i++) {
    eval(arguments[i]);
  }
}

次のように関数を呼び出します。

evaluateExpressions(expr1, expr2, expr3, ...)

ただし、次のようにすることもできます。

eval(expr1 + expr2 + expr3 + ...)

また

eval([expr1, expr1, expr3, ...].join(';'))

少なくとも関数内から呼び出すことは、変数が誤ってグローバルにならないことを意味します。

ああ、ほとんど忘れていましたが、eval は悪です。;-)

于 2013-10-17T01:37:24.120 に答える