var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
このように出力されます。
私の値です。3 私の値3 私の価値3
一方、出力してほしいのは 私の値:0 私の値: 1 私の値:2
イベントリスナーを使用しているために関数の実行が遅れる場合にも同様の問題が発生します。
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
... また、Promisesなどを使った非同期コードもあります。
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
この基本的な問題を解決するにはどうしたらいいのでしょうか?
さて、問題は、それぞれの無名関数の中の変数i
が、関数の外の同じ変数に束縛されていることです。
各関数内の変数を、関数外の独立した不変の値に束縛したいのです。
var funcs = [];
function createfunc(i) {
return function() {
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
<! -- スニペットの終了 -->
JavaScriptにはブロックスコープがなく、ファンクションスコープしかないので、関数の生成を新しい関数でラップすることで、「i」の値が意図した通りになるようにしています。
Array.prototype.forEach関数が比較的広く利用できるようになった(2015年)ことで、主に値の配列に対する反復を伴う状況において、
.forEach()`は、反復ごとに個別のクロージャを得るためのクリーンで自然な方法を提供していることに注目したいと思います。つまり、値を含む何らかの配列(DOM参照、オブジェクト、その他)があると仮定して、各要素に固有のコールバックを設定するという問題が発生した場合、次のようにすることができます。
var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
// ... code code code for this one element
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});
.forEach`ループで使用されるコールバック関数の呼び出しは、それぞれ独立したクロージャになるということです。ハンドラに渡されるパラメータは、反復の特定のステップに固有の配列要素です。これを非同期のコールバックで使用すれば、反復の他のステップで確立された他のコールバックと衝突することはありません。
jQueryをお使いの場合は、$.each()
関数で同様の機能を利用できます。
let
ECMAScript 6 (ES6) では、新しい let
および const
キーワードが導入され、var
ベースの変数とは異なるスコープで扱われるようになりました。例えば、let
ベースのインデックスを持つループでは、ループを繰り返すたびに新しいi
の値が得られ、各値はループ内にスコープされるので、あなたのコードは期待通りに動作するでしょう。多くの資料がありますが、2ality's block-scoping postが素晴らしい情報源としてお勧めです。
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
ただし、IE9-IE11とEdge 14以前のEdgeはlet
をサポートしていますが、上記のように間違っているので注意してください(毎回新しいi
を作成しないので、上記のすべての関数はvar
を使用したときのように3を記録します)。Edge 14ではようやくそれが実現しました。
試してみてください。
。var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
console.log("My value: " + index);
};
}(i));
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
<! -- スニペットの終了 -->
編集(2014年)。
個人的には、@Austさんの.bind
の使用に関する最近の回答が、この種のことをするのに最も良い方法だと思います。lo-dash/underscoreの_.partial
も、bind
のthisArg
を使う必要がない、あるいは使いたくない場合には有効です。