60

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

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 を試してみました。誰かが私にこの振る舞いを説明してもらえますか?

4

7 に答える 7

61

追加の匿名関数を追加して、Jon の回答を修正しました。

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 でメモリ リークが発生します。

于 2009-03-13T16:57:47.770 に答える
9

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

var closures = [];

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

function create() {
    for (var i = 0; i < 5; i++) {
        createClosure(i);
    }
}
于 2009-03-13T16:39:43.633 に答える
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);
        });
    })();
}
于 2009-03-13T16:42:50.223 に答える
6

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

編集: Skeet 氏による この記事では、クロージャーについてある程度詳しく説明し、特にこの問題について、私がここで説明するよりもはるかに有益な方法で対処しています。 ただし、Javascript と C# のクロージャの処理方法には微妙な違いがあるので注意してください。 この問題に関する彼の説明については、「キャプチャ戦略の比較: 複雑さとパワー」というセクションにスキップしてください。

于 2009-03-13T16:30:09.507 に答える
4

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);
于 2009-03-13T17:24:33.243 に答える
1

内部関数を定義するか、変数に代入するだけです:

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

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

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

于 2010-10-27T23:18:39.233 に答える
-1

結果を達成するためにすべきことは次のとおりです。

<script>
var closures = [];
function create() {  
    for (var i = 0; i < 5; i++) {   
        closures[i] = function(number) {      
        alert("i = " + number);   
        };  
    }
}
function run() {  
    for (var i = 0; i < 5; i++) {   
        closures[i](i); 
    }
}
create();
run();
</script>
于 2009-03-13T16:40:56.720 に答える