[解決済み] ループ内のJavaScriptクロージャ - シンプルな実用例
質問
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(var f of fns){
f();
}
この基本的な問題を解決するにはどうしたらいいのでしょうか?
どのように解決するのか?
さて、問題は、変数
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"の値が意図したとおりになることを保証しているのです。
関連
-
親子コンポーネント通信を解決する3つのVueスロット
-
jsを使った簡単な照明スイッチのコード
-
vueのグローバルがscss(mixin)を導入。
-
vueにおけるfilterの適用シーンについて解説します。
-
[解決済み] JavaScript の Promise が解決するのを待ってから機能を再開するにはどうすればよいですか?
-
[解決済み] Google Maps JS API v3 - シンプルな複数マーカーの例
-
[解決済み] JavaScriptでネストしたループから抜け出すには?
-
[解決済み] JavaScriptのFor.Inループ - キーと値のペア
-
[解決済み] ループ内での変数宣言、グッドプラクティスかバッドプラクティスか?
-
[解決済み】JavaScriptでクロージャの実用的な使い方は?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Vue+ElementUIによる大規模なフォームの処理例
-
vueのグローバルがscss(mixin)を導入。
-
vue ディレクティブ v-html と v-text
-
Vueのクラススタイルの使い方の詳細
-
[解決済み] Error : 未定義のプロパティ 'map' を読み取ることができません。
-
[解決済み] 配列の結合時に未定義のプロパティ 'push' を読み込むことができない
-
[解決済み】TypeError: Router.use() はミドルウェアの関数を要求しているが、Object を取得した。
-
[解決済み】forループのsetTimeoutで連続した値が表示されない【重複あり
-
[解決済み】Javascriptのinfamous Loop問題?重複
-
[解決済み] javascriptのforループ内の非同期処理 [重複]について