web-dev-qa-db-ja.com

JavaScriptはローカル変数によるクロージャーをサポートしていませんか?

私はこのコードについて非常に困惑しています:

var closures = [];
function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = function() {
      alert("i = " + i);
    };
  }
}

function run() {
  for (var i = 0; i < 5; i++) {
    closures[i]();
  }
}

create();
run();

私の理解から、0、1、2、3、4を出力する必要があります(これはクロージャの概念ではありませんか?)。

代わりに、5,5,5,5,5と出力します。

RhinoとFirefoxを試しました。

誰かがこの動作を私に説明できますか?事前にTHX。

57
qollin

匿名関数を追加して、ジョンの答えを修正しました。

function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = (function(tmp) {
        return function() {
          alert("i = " + tmp);
        };
    })(i);
  }
}

説明は、JavaScriptのスコープがブロックレベルではなく関数レベルであることであり、クロージャーを作成することは、囲まれたスコープが囲まれた関数の字句環境に追加されることを意味します。

ループが終了すると、関数レベルの変数iの値は5になり、それが内部関数で「認識」されます。


余談ですが、特にループでの不要な関数オブジェクトの作成に注意する必要があります。これは非効率的であり、DOMオブジェクトが関係している場合、循環参照を作成するのは簡単なので、Internet Explorerでメモリリークが発生します。

60
Christoph

私はこれがあなたが望むものかもしれないと思います:

var closures = [];

function createClosure(i) {
    closures[i] = function() {
        alert("i = " + i);
    };
}

function create() {
    for (var i = 0; i < 5; i++) {
        createClosure(i);
    }
}
9

解決策は、配列のプッシュをラップする自己実行ラムダを用意することです。また、そのラムダの引数としてiを渡します。自己実行ラムダ内のiの値は、元のiの値を覆い隠し、すべてが意図したとおりに機能します。

function create() {
    for (var i = 0; i < 5; i++) (function(i) {
        closures[i] = function() {
            alert("i = " + i);
        };
    })(i);
}

別の解決策は、iの正しい値を取得し、それを最後のラムダで「キャッチ」される別の変数に割り当てる、さらに別のクロージャーを作成することです。

function create() {
    for (var i = 0; i < 5; i++) (function() {
        var x = i;

        closures.Push(function() {
            alert("i = " + x);
        });
    })();
}
9
Ionuț G. Stan

はい、クロージャーはここで機能しています。作成する関数をループするたびに、iを取得します。作成する各関数は同じiを共有します。あなたが見ている問題は、それらがすべて同じiを共有しているため、同じキャプチャされた変数であるため、iの最終値も共有していることです。

編集:この記事 スキート氏によるクロージャについてある程度説明し、特にこの問題に多くの方法で対処しますもっと参考になれば、私はここにいます。 ただし、JavascriptとC#のハンドルクロージャには微妙な違いがあるので注意してください。この問題についての彼の説明は、「キャプチャ戦略の比較:複雑さvsパワー」と呼ばれるセクション。

6
Andrew Hare

John Resigの Learning Advanced JavaScript は、このことなどについて説明しています。それはJavaScriptについて多くを説明するインタラクティブなプレゼンテーションであり、例は読んで実行するのが楽しいです。

クロージャについての章があり、 この例 はあなたのものによく似ています。

これが壊れた例です:

var count = 0; 
for ( var i = 0; i < 4; i++ ) { 
  setTimeout(function(){ 
    assert( i == count++, "Check the value of i." ); 
  }, i * 200); 
}

そして修正:

var count = 0; 
for ( var i = 0; i < 4; i++ ) (function(i){ 
  setTimeout(function(){ 
    assert( i == count++, "Check the value of i." ); 
  }, i * 200); 
})(i);

内部関数を定義するか、それをいくつかの変数に割り当てるだけです。

closures[i] = function() {...

実行コンテキスト全体のプライベートコピーは作成されません。コンテキストは、最も近い外部関数がexitingになるまでコピーされません(この時点で、これらの外部変数はガベージコレクションされる可能性があるため、コピーを取得することをお勧めします)。

これが、内部関数の周りに別の関数をラップすることが機能する理由です-中間の人は実際に実行して終了し、最も内側の関数を呼び出して自分のスタックのコピーを保存します。

1
John Rice