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>
… または非同期コード(例:Promise を使用):
// 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));
}
for in
これは、 およびfor of
ループでも明らかです。
const arr = [1,2,3];
const fns = [];
for (var i in arr){
fns.push(() => console.log("index:", i));
}
for (var v of arr){
fns.push(() => console.log("value:", v));
}
for (const n of arr) {
var obj = { number: n }; // or new MyLibObject({ ... })
fns.push(() => console.log("n:", n, "|", "obj:", JSON.stringify(obj)));
}
for(var f of fns){
f();
}
この基本的な問題の解決策は何でしょうか?
ベストアンサー1
問題は、i
各匿名関数内の変数が、関数外の同じ変数にバインドされていることです。
ES6ソリューション:let
ECMAScript 6 (ES6)では、 ベースの変数とは異なるスコープを持つ新しいキーワードlet
とキーワードが導入されました。たとえば、 ベースのインデックスを持つループでは、ループの各反復でループスコープを持つ新しい変数が作成されるため、コードは期待どおりに動作します。多くのリソースがありますが、const
var
let
i
2ality のブロックスコープの投稿素晴らしい情報源として。
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
ただし、IE9 ~ IE11 および Edge 14 より前の Edge は をサポートしていますlet
が、上記は間違っていることに注意してください (毎回新しい を作成しないためi
、 を使用した場合と同様に、上記のすべての関数は 3 をログに記録しますvar
)。Edge 14 では最終的に正しく実行されます。
ES5.1 ソリューション: forEach
この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()
関数により同様の機能が提供されます。
古典的な解決策: クロージャ
やりたいことは、各関数内の変数を、関数外の別の不変の値にバインドすることです。
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」の値が意図したとおりに維持されることが保証されます。