質問:
|
でもこれは以下を出力してしまう:
My value: 3
My value: 3
My value: 3
でも僕が欲しいのは
My value: 0
My value: 1
My value: 2
同じ問題は、関数の実行がイベントリスナーを使用することによって遅延してしまう時にも起こる:
|
|
この基本的な問題の解決策ってある??
回答1:
えっと、問題は、それぞれの匿名の関数の中にある変数i
が関数の外部にある同じ変数にバインドされてるってことだよ。
君がしたいのは、各関数内の変数を独立した変数にバインドすることだと思う。そうすれば関数の外で値が変わらない:
|
JavaScriptにはブロックスコープがないので(関数スコープのみ)、新しい関数の中で関数の作成をラップすると、"i"
は君が意図した通りになるように気をつけないといけない。
Update:
つまり、.forEach
ループと一緒に使われたコールバック関数の各呼び出しが独自のクロージャになる。そのハンドラに渡されたパラメータは、その特定の反復ステップに固有の配列要素になってる。非同期コールバックで使用されている場合、それはその反復の他のステップで確立された他のコールバックどれとも衝突しない。
もしjQueryで作業している場合なら、$ .each()
関数で同じようなことができるよ。
Update 2: JavaScriptの最新バージョンであるECMAScript 6(ES6)は、多くのブラウザとバックエンドシステムで実装され始めてる。古いシステムで新しい機能を使用できるようにES6からES5に変換するBabelのようなトランスパイライザーとかももあります。
ES6では、var
ベースの変数とは違うスコープを持つ新しい let
とconst
キーワードが導入されてる。たとえば、 let
ベースのインデックスを持つループでは、それぞれループを通る反復は新しい値「i」を持ち、各値はループの内側にスコープがあるので、君のコードは期待どおりに動作するはず。いろんなリソースがあると思うけど、僕は2alityのブロックスコープに関する記事の素晴らしいソースとしておすすめするよ。
|
でも注意すべきはIE9-IE11とEdge14以前のEdgeは、let
をサポートしてるけど、上記の間違いをしてしまう(毎回新しい i
を作成しないので、上で書いたどの関数もvar
を使用した場合と同様に「3」を出力する)。Edge 14では最終的にそれは解決してる。
回答2:
最初にループの外側で関数を作り、その結果をループ内でバインドさせる方がパフォーマンスがいいと思うよ。
|
Source: JavaScript closure inside loops – simple practical example