私はこのコードについて非常に困惑しています:
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。
匿名関数を追加して、ジョンの答えを修正しました。
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でメモリリークが発生します。
私はこれがあなたが望むものかもしれないと思います:
var closures = [];
function createClosure(i) {
closures[i] = function() {
alert("i = " + i);
};
}
function create() {
for (var i = 0; i < 5; i++) {
createClosure(i);
}
}
解決策は、配列のプッシュをラップする自己実行ラムダを用意することです。また、そのラムダの引数として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);
});
})();
}
はい、クロージャーはここで機能しています。作成する関数をループするたびに、i
を取得します。作成する各関数は同じi
を共有します。あなたが見ている問題は、それらがすべて同じi
を共有しているため、同じキャプチャされた変数であるため、i
の最終値も共有していることです。
編集:この記事 スキート氏によるクロージャについてある程度説明し、特にこの問題に多くの方法で対処しますもっと参考になれば、私はここにいます。 ただし、JavascriptとC#のハンドルクロージャには微妙な違いがあるので注意してください。この問題についての彼の説明は、「キャプチャ戦略の比較:複雑さvsパワー」と呼ばれるセクション。
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になるまでコピーされません(この時点で、これらの外部変数はガベージコレクションされる可能性があるため、コピーを取得することをお勧めします)。
これが、内部関数の周りに別の関数をラップすることが機能する理由です-中間の人は実際に実行して終了し、最も内側の関数を呼び出して自分のスタックのコピーを保存します。