var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
それはこれを出力します:
私の価値:3
私の価値:3
私の価値:3
私はそれを出力したいのですが:
私の価値:0
私の価値:1
私の価値:2
同じ問題は、関数の実行の遅延がイベントリスナーの使用によって引き起こされる場合にも発生します。
var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) { // let's create 3 functions
buttons[i].addEventListener("click", function() { // as event listeners
console.log("My value: " + i); // each should log its value.
});
}
<button>0</button><br>
<button>1</button><br>
<button>2</button>
…または非同期コード、約束を使う:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for(var i = 0; i < 3; i++){
wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}
この基本的な問題に対する解決策は何ですか?
問題は、各無名関数内の変数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++) {
funcs[j](); // and now let's run each one to see
}
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のブロックスコーピングポスト を素晴らしい情報源としてお勧めします。
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
ただし、Edge 14より前のIE9-IE11およびEdgeはlet
をサポートしていますが、間違った方法で取得します(毎回新しいi
を作成するわけではないため、var
を使用した場合のように上記のすべての関数は3を記録します)。エッジ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's .bind
の使用に関する最近の回答 が、このようなことをする最善の方法だと思います。あなたがbind
のthisArg
を必要としない、あるいは混乱させたくないときは、ローダッシュ/アンダースコアの_.partial
もあります。
まだ言及されていない別の方法は Function.prototype.bind
の使用です。
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function(x) {
console.log('My value: ' + x);
}.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
_ update _
@squintと@mekdevで指摘されているように、最初にループの外側に関数を作成し、次にループ内で結果をバインドすることによって、パフォーマンスが向上します。
function log(x) {
console.log('My value: ' + x);
}
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = log.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
即時呼び出し関数式 を使用して、インデックス変数を囲む最も簡単で読みやすい方法を示します。
for (var i = 0; i < 3; i++) {
(function(index) {
console.log('iterator: ' + index);
//now you can also loop an ajax call here
//without losing track of the iterator value: $.ajax({});
})(i);
}
これはイテレータi
をindex
として定義している無名関数に送ります。これによりクロージャーが作成され、変数i
は後でIIFE内の非同期機能で使用するために保存されます。
パーティーには少々時間がかかりましたが、私は今日この問題を調査していて、多くの答えがJavascriptがどのようにスコープを扱うのか完全には解決していないことに気づきました。
他の多くの人が述べたように、問題は内部関数が同じi
変数を参照していることです。それでは、反復ごとに新しいローカル変数を作成し、その代わりに内部関数に参照させるのではないのですか。
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
var ilocal = i; //create a new local variable
funcs[i] = function() {
console.log("My value: " + ilocal); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
各内部関数がi
に割り当てられた最後の値を出力する以前のように、各内部関数はilocal
に割り当てられた最後の値を出力するようになりました。しかし、それぞれの反復がそれ自身のilocal
を持つべきではありませんか?
結局のところ、それが問題です。各反復は同じスコープを共有しているため、最初の反復以降の反復はすべてilocal
を上書きするだけです。 _ mdn _ から:
重要:JavaScriptにはブロックスコープがありません。ブロックで導入された変数はそれを含んでいる関数またはスクリプトにスコープされ、それらを設定する効果はブロック自体を超えて持続します。つまり、ブロックステートメントはスコープを導入しません。 "スタンドアローン"ブロックは有効な構文ですが、JavaScriptでスタンドアローンブロックを使用することは望ましくありません。CやJavaのブロックのようなものであれば、意図したとおりには機能しません。
強調するために繰り返した:
JavaScriptにはブロックスコープがありません。ブロックで導入された変数は、それを含む関数またはスクリプトにスコープされます。
これを確認するには、各反復で宣言する前にilocal
を確認します。
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
console.log(ilocal);
var ilocal = i;
}
これがまさにこのバグがとてもトリッキーな理由です。あなたが変数を再宣言していても、Javascriptはエラーを投げません、そして、JSLintは警告を投げさえしません。これがこれを解決する最良の方法がクロージャを利用することである理由でもあります。これは本質的にJavascriptでは、内部スコープが外部スコープを「囲む」ので内部関数が外部変数にアクセスするという考えです。
これはまた、内部関数が外部変数を「保持」し、外部関数が戻ってもそれらを有効に保つことを意味します。これを利用するために、ラッパー関数を作成して純粋に新しいスコープを作成し、その新しいスコープ内でilocal
を宣言し、ilocal
を使用する内部関数を返します(以下でさらに説明します)。
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = (function() { //create a new scope using a wrapper function
var ilocal = i; //capture i into a local var
return function() { //return the inner function
console.log("My value: " + ilocal);
};
})(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
ラッパー関数の中に内部関数を作成すると、その内部関数だけがアクセスできるプライベート環境、つまり「クロージャー」が与えられます。したがって、ラッパー関数を呼び出すたびに、ilocal
変数が衝突して互いに上書きされないように、独自の独立した環境で新しい内部関数を作成します。いくつかのマイナーな最適化は、他の多くのSOユーザーが与えた最終的な答えを与えます。
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
return function() { //return the inner function
console.log("My value: " + ilocal);
};
}
更新
現在ES6が主流になっているので、新しいlet
キーワードを使用してブロックスコープの変数を作成できます。
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
funcs[i] = function() {
console.log("My value: " + i); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
funcs[j]();
}
それが今どれほど簡単か見てください!詳細については この回答 を参照してください。これは私の情報に基づいています。
ES6は現在広くサポートされているので、この質問に対する最良の答えは変わりました。このような状況のために、ES6ではlet
キーワードとconst
キーワードが用意されています。クロージャをいじるのではなく、let
を使用してループスコープ変数を次のように設定できます。
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
val
は、そのループの特定の順番に固有のオブジェクトを指し、追加のクロージャ表記なしで正しい値を返します。これは明らかにこの問題を著しく単純化します。
const
はlet
と似ていますが、最初の代入後に変数名を新しい参照に再バインドできないという追加の制限があります。
最新バージョンのブラウザをターゲットとしているユーザ向けに、ブラウザサポートが追加されました。 const
/let
は現在、最新のFirefox、Safari、Edge、およびChromeでサポートされています。 Nodeでもサポートされており、Babelのようなビルドツールを利用することでどこでも使用することができます。ここで実用的な例を見ることができます: http://jsfiddle.net/ben336/rbU4t/2/
ドキュメントはこちら:
ただし、IE9〜IE11およびEdge 14より前のEdgeではlet
をサポートしていますが、上記のように間違っています(毎回新しいi
を作成するわけではないため、var
を使用した場合のように上記の関数はすべて3を記録します)。エッジ14はついにそれを正しくします。
別の言い方をすれば、関数内のi
は、関数の作成時ではなく、関数の実行時にバインドされるということです。
クロージャを作成するとき、i
は外側のスコープで定義された変数への参照であり、クロージャを作成したときのようなコピーではありません。実行時に評価されます。
他の答えのほとんどはあなたのために値を変更しない別の変数を作成することによって回避する方法を提供します。
わかりやすくするために説明を追加したいと思いました。解決策としては、個人的には、ここでの回答からそれを実行するための最も自明の方法であるため、私はHartoのものを使用します。投稿されたコードはどれでも動作しますが、なぜ私は新しい変数を宣言するのか(Freddyと1800年代)、奇妙な埋め込みクロージャ構文(apphacker)を持つのか説明するためにコメントの山を書く必要があります。
あなたが理解する必要があるのは、JavaScriptの変数の範囲が関数に基づいているということです。これは、あなたがブロックスコープを持っていて、その変数をforの中のものにコピーするだけでうまくいくというc#と言うこととは重要な違いです。
Apphackerの答えのように関数を返すことを評価する関数でそれをラッピングすると、変数が関数スコープを持つようになるので、うまくいきます。
Varの代わりにletキーワードもあります。これにより、ブロックスコープルールを使用できます。その場合、forの中に変数を定義するとうまくいくでしょう。そうは言っても、letキーワードは互換性のため実用的な解決策ではありません。
var funcs = {};
for (var i = 0; i < 3; i++) {
let index = i; //add this
funcs[i] = function() {
console.log("My value: " + index); //change to the copy
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
これはBjorn(apphacker)に似たテクニックの別のバリエーションで、変数として渡すのではなく関数の中に変数値を代入できます。
for (var i = 0; i < 3; i++) {
funcs[i] = (function() {
var index = i;
return function() {
console.log("My value: " + index);
}
})();
}
どのようなテクニックを使用しても、index
変数は内部関数の返されたコピーにバインドされた一種の静的変数になります。つまり、値の変更は呼び出し間でも維持されます。とても便利です。
これは、JavaScriptでクロージャを使用することによる一般的な間違いについて説明しています。
検討してください:
function makeCounter()
{
var obj = {counter: 0};
return {
inc: function(){obj.counter ++;},
get: function(){return obj.counter;}
};
}
counter1 = makeCounter();
counter2 = makeCounter();
counter1.inc();
alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0
makeCounter
が呼び出されるたびに、{counter: 0}
によって新しいオブジェクトが作成されます。また、新しいオブジェクトを参照するためのobj
の新しいコピーも作成されます。したがって、counter1
とcounter2
は互いに独立しています。
ループ内でクロージャを使用するのは難しいです。
検討してください:
var counters = [];
function makeCounters(num)
{
for (var i = 0; i < num; i++)
{
var obj = {counter: 0};
counters[i] = {
inc: function(){obj.counter++;},
get: function(){return obj.counter;}
};
}
}
makeCounters(2);
counters[0].inc();
alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1
counters[0]
とcounters[1]
はnot独立していることに注意してください。実際、それらは同じobj
上で動作します。
これは、おそらくパフォーマンス上の理由から、ループのすべての反復で共有されるobj
のコピーが1つしかないためです。 {counter: 0}
は各反復で新しいオブジェクトを作成しますが、obj
の同じコピーは最新のオブジェクトへの参照で更新されます。
解決策は別のヘルパー関数を使うことです:
function makeHelper(obj)
{
return {
inc: function(){obj.counter++;},
get: function(){return obj.counter;}
};
}
function makeCounters(num)
{
for (var i = 0; i < num; i++)
{
var obj = {counter: 0};
counters[i] = makeHelper(obj);
}
}
これは、関数スコープ変数内のローカル変数、および関数引数変数がエントリ時に新しいコピーに割り当てられるためです。
詳細な議論については、 JavaScriptのクロージャーの落とし穴と使い方 を見てください。
最も簡単な解決策は、
使用する代わりに:
var funcs = [];
for(var i =0; i<3; i++){
funcs[i] = function(){
alert(i);
}
}
for(var j =0; j<3; j++){
funcs[j]();
}
3回、「2」と表示されます。これは、forループで作成された無名関数が同じクロージャを共有し、そのクロージャではi
の値が同じであるためです。共有クロージャを防ぐためにこれを使用してください。
var funcs = [];
for(var new_i =0; new_i<3; new_i++){
(function(i){
funcs[i] = function(){
alert(i);
}
})(new_i);
}
for(var j =0; j<3; j++){
funcs[j]();
}
この背景にある考えは、forループの本体全体を _ iife _ (即時起動関数式)でカプセル化し、new_i
をパラメータとして渡してi
として取得することです。無名関数はすぐに実行されるので、i
の値は無名関数内で定義されている関数ごとに異なります。
この問題は、この問題を抱えている元のコードに最小限の変更を加えるだけで済むため、このような問題に適しているようです。実際、これは仕様によるもので、まったく問題にならないはずです。
配列なし
ループのための余分なものはありません
for (var i = 0; i < 3; i++) {
createfunc(i)();
}
function createfunc(i) {
return function(){console.log("My value: " + i);};
}
これはforEach
を使用する簡単な解決策です(IE9に戻ります)。
var funcs = [];
[0,1,2].forEach(function(i) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
})
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
プリント:
My value: 0 My value: 1 My value: 2
OPによって示されるコードの主な問題は、i
が2番目のループまで読み込まれないことです。実証するために、コードの中にエラーがあるのを想像してみてください。
funcs[i] = function() { // and store them in funcs
throw new Error("test");
console.log("My value: " + i); // each should log its value.
};
funcs[someIndex]
が()
が実行されるまで、実際にはエラーは発生しません。これと同じロジックを使用して、i
の値もこの時点まで収集されないことは明らかです。元のループが終了すると、i++
はi
を3
の値にし、その結果、条件i < 3
が失敗し、ループが終了します。この時点では、i
は3
なので、funcs[someIndex]()
が使用され、i
が評価されると、毎回3になります。
これを乗り越えるためには、i
が発生したときにそれを評価する必要があります。これはすでにfuncs[i]
の形で行われていることに注意してください(3つのユニークなインデックスがあります)。この価値を捉えるにはいくつかの方法があります。 1つは、すでにいくつかの方法でここに示されている関数にパラメータとして渡すことです。
別の選択肢は、変数を閉じることができるようになる関数オブジェクトを構築することです。それはこうして達成することができます
funcs[i] = new function() {
var closedVariable = i;
return function(){
console.log("My value: " + closedVariable);
};
};
JavaScript関数は宣言時にアクセス権を持つスコープを「閉じ」、そのスコープ内の変数が変更されてもそのスコープへのアクセスを保持します。
var funcs = []
for (var i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
for (var k = 0; k < 3; k += 1) {
funcs[k]()
}
上記の配列の各関数は、グローバルスコープを閉じます(global、単にそれが宣言されているスコープであるためです)。
後でこれらの関数が呼び出され、グローバルスコープ内のi
の最新の値が記録されます。それが閉鎖の魔法、そしてフラストレーションです。
"JavaScript関数は、宣言されたスコープを閉じ、そのスコープ内の変数値が変更されてもそのスコープへのアクセスを保持します。"
let
の代わりにvar
を使用すると、for
ループが実行されるたびに新しいスコープが作成され、クローズする関数ごとに別々のスコープが作成されます。他のさまざまなテクニックが追加の機能で同じことをします。
var funcs = []
for (let i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
for (var k = 0; k < 3; k += 1) {
funcs[k]()
}
(let
は変数の範囲をブロックにします。ブロックは中括弧で示されますが、forループの場合は初期化変数i
は中括弧で宣言されていると見なされます。)
さまざまな解決策を読み終えた後、これらの解決策が機能する理由は scope chain の概念に頼ることであることを付け加えたいと思います。それはJavaScriptが実行中に変数を解決する方法です。
var
およびそのarguments
によって宣言されたすべてのローカル変数からなるスコープを形成します。window
に属するまで続きます。初期コードでは:
funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function inner() { // function inner's scope contains nothing
console.log("My value: " + i);
};
}
console.log(window.i) // test value 'i', print 3
funcs
が実行されると、スコープチェーンはfunction inner -> global
になります。変数i
はfunction inner
内で見つけることができないため(var
を使用して宣言することも引数として渡すこともしない)、i
の値が最終的にグローバルスコープのwindow.i
に見つかるまで検索を続けます。
それを外側の関数にラップすることで、 harto didのようなヘルパー関数を明示的に定義するか、 Bjorn didのような無名関数を使用することができます。
funcs = {};
function outer(i) { // function outer's scope contains 'i'
return function inner() { // function inner, closure created
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = outer(i);
}
console.log(window.i) // print 3 still
funcs
が実行されると、スコープチェーンはfunction inner -> function outer
になります。今回はi
はforループの中で3回実行される外部関数のスコープ内にあり、毎回i
が正しくバインドされています。内部実行時にwindow.i
の値は使用されません。
より多くの詳細は見つけることができます ここ
ループにクロージャを作成する際の一般的な間違いや、クロージャが必要な理由とパフォーマンスの考慮事項が含まれます。
ES6の新機能により、ブロックレベルのスコープが管理されます。
var funcs = [];
for (let i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (let j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
OPの質問のコードは、let
ではなくvar
に置き換えられます。
ローカル変数の使用を(再)回避するためにforEach
関数を使用することを提案した人がまだいないことに驚きます。実際、私はこの理由でもうfor(var i ...)
を使っていません。
[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3
// mapの代わりにforEach
を使用するように編集しました。
この質問は本当にJavaScriptの歴史を示しています!これで、矢印関数によるブロックスコープを避け、Objectメソッドを使ってDOMノードから直接ループを処理することができます。
const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())
const buttons = document.getElementsByTagName("button");
Object
.keys(buttons)
.map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>
まず最初に、このコードの何が問題なのかを理解してください。
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
ここでfuncs[]
配列が初期化されるとき、i
がインクリメントされ、funcs
配列が初期化され、func
配列のサイズが3になるので、i = 3,
となります。これでfuncs[j]()
が呼び出されると、それは再び3にインクリメントされている変数i
を使用しています。
これを解決するために、たくさんの選択肢があります。以下はそのうちの2つです。
i
をlet
で初期化するか、新しい変数index
をlet
で初期化してi
と等しくすることができます。そのため、呼び出しが行われているときはindex
が使用され、そのスコープは初期化後に終了します。そして呼び出しのために、index
は再び初期化されます。
var funcs = [];
for (var i = 0; i < 3; i++) {
let index = i;
funcs[i] = function() {
console.log("My value: " + index);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
他のオプションは、実際の関数を返すtempFunc
を導入することです。
var funcs = [];
function tempFunc(i){
return function(){
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = tempFunc(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
var
とlet
を1つずつ宣言したときに実際に何が起こるかを調べます。
var
<script>
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function () {
debugger;
console.log("My value: " + i);
};
}
console.log(funcs);
</script>
F12を押してクロムコンソールウィンドウを開き、ページを更新します。 [[Scopes]]
という名前のプロパティが表示されますので、それを展開してください。 "Global"
という名前の1つの配列オブジェクトが表示されたら、それを展開します。値3を持つオブジェクトに宣言されたプロパティ'i'
が見つかります。
結論:
'var'
を使用して変数を宣言すると、それはグローバル変数になります(コンソールウィンドウでi
またはwindow.i
を入力して確認できます。3が返されます)。console.log("My value: " + i)
はそのGlobal
オブジェクトから値を取得して結果を表示します。'var'
を'let'
に置き換えます
<script>
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
debugger;
console.log("My value: " + i);
};
}
console.log(funcs);
</script>
同じことをする、スコープに行きなさい。これで、2つのオブジェクト"Block"
と"Global"
が表示されます。 Block
オブジェクトを展開すると、 'i'が定義されているのがわかります。奇妙なことに、すべての関数について、i
の値が異なる(0、1、2)ということがあります。
結論:
関数の外側でもループの内側でも'let'
を使用して変数を宣言すると、この変数はグローバル変数にはならず、同じ関数に対してのみ利用可能なBlock
レベルの変数になります。関数を呼び出すと、関数ごとにi
の値が異なります。
どれだけうまくいくかについての詳細は、素晴らしいビデオチュートリアル https://youtu.be/71AtaJpJHw0 をご覧ください。
あなたのオリジナルの例がうまくいかなかった理由は、あなたがループの中で作成したすべてのクロージャが同じフレームを参照していたからです。実際には、1つのオブジェクトに対して3つのメソッドを持ち、1つのi
変数のみを使用します。それらはすべて同じ値を印刷しました。
closure structureを使用すると、余分なforループが少なくなります。単一のforループでそれを行うことができます。
var funcs = [];
for (var i = 0; i < 3; i++) {
(funcs[i] = function() {
console.log("My value: " + i);
})(i);
}
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function(param) { // and store them in funcs
console.log("My value: " + param); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](j); // and now let's run each one to see with j
}
私はforEach
関数を使用することを好みます。これは疑似範囲を作成するための独自のクロージャを持ちます。
var funcs = [];
new Array(3).fill(0).forEach(function (_, i) { // creating a range
funcs[i] = function() {
// now i is safely incapsulated
console.log("My value: " + i);
};
});
for (var j = 0; j < 3; j++) {
funcs[j](); // 0, 1, 2
}
それは他の言語の範囲よりも酷いように見えますが、私見は他のソリューションよりも怖いものではありません。
query-js (*)のように、データのリストに宣言型モジュールを使用できます。このような状況では、私は個人的に宣言的アプローチの方が驚くことは少ないと思います
var funcs = Query.range(0,3).each(function(i){
return function() {
console.log("My value: " + i);
};
});
その後、2回目のループを使用して期待どおりの結果を得ることができます。または、実行することもできます。
funcs.iterate(function(f){ f(); });
(*)私はquery-jsの作者であり、したがってそれを使うことに偏っているので、宣言的アプローチのためだけにこのライブラリの推奨として私の言葉を使わないでください。
あなたのコードは機能しません。
Create variable `funcs` and assign it an empty array;
Loop from 0 up until it is less than 3 and assign it to variable `i`;
Push to variable `funcs` next function:
// Only Push (save), but don't execute
**Write to console current value of variable `i`;**
// First loop has ended, i = 3;
Loop from 0 up until it is less than 3 and assign it to variable `j`;
Call `j`-th function from variable `funcs`:
**Write to console current value of variable `i`;**
// Ask yourself NOW! What is the value of i?
問題は、関数が呼び出されたときの変数i
の値は何ですか?最初のループはi < 3
の条件で作成されるため、条件が偽になるとすぐに停止します。したがって、i = 3
です。
関数が作成された時点で、それらのコードは実行されず、後で保存されるだけであることを理解する必要があります。そのため、後でそれらが呼び出されると、インタプリタはそれらを実行して「現在のi
の値は何ですか?」と尋ねます。
ですから、あなたの目標は、最初にi
の値をfunctionに保存し、その後に限ってその関数をfuncs
に保存することです。これは、たとえば次のようにして実行できます。
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function(x) { // and store them in funcs
console.log("My value: " + x); // each should log its value.
}.bind(null, i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
このように、各関数はそれ自身の変数x
を持ち、各反復においてこのx
をi
の値に設定します。
これは、この問題を解決するための複数の方法のうちの1つにすぎません。
多くの解決策は正しいように見えますが、 Currying
と呼ばれているわけではありません。これは、このような状況での関数型プログラミング設計パターンです。ブラウザによってはバインドよりも3〜10倍速くなります。
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
function curryShowValue(i) {
return function showValue() {
console.log("My value: " + i);
}
}
異なるブラウザでのパフォーマンスの向上 を参照してください。
Varの代わりにlet(block-scope)を使用してください。
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
さらに別の解決策:別のループを作成するのではなく、単にthis
をreturn関数にバインドします。
var funcs = [];
function createFunc(i) {
return function() {
console.log('My value: ' + i); //log value of i.
}.call(this);
}
for (var i = 1; i <= 5; i++) { //5 functions
funcs[i] = createFunc(i); // call createFunc() i=5 times
}
this をバインドすることで、問題も解決します。
これは非同期コードでしばしば遭遇する問題で、変数i
は可変であり、関数呼び出しが行われた時点でi
を使ったコードが実行され、i
はその最後の値に変更されます。 loopは クロージャ を作成し、i
は3(for
ループの上限+ 1)に等しくなります。
これを回避するには、反復ごとにi
の値を保持し、コピーi
を強制する関数を作成します(これはプリミティブなので、役立つ場合はスナップショットと考えてください)。
Varキーワードをletに変更するだけです。
varは関数スコープです。
ブロックスコープです。
コーディングを開始すると、forループが反復してiの値を3に割り当てます。これはコード全体で3のままです。ノード内のスコープについてもっと読むことをお勧めします(let、var、constなど)。
funcs = [];
for (let i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] =async function() { // and store them in funcs
await console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
カウンタが原始的
次のようにコールバック関数を定義しましょう。
// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
for (var i=0; i<2; i++) {
setTimeout(function() {
console.log(i);
});
}
}
test1();
// 2
// 2
タイムアウトが完了すると、両方に2が印刷されます。これは、コールバック関数が、関数が定義されている lexical scope に基づいて値にアクセスするためです。
コールバックが定義されている間に値を渡して保存するには、コールバックが呼び出される前に値を保存するために クロージャ を作成します。これは次のようにして行うことができます。
function test2() {
function sendRequest(i) {
setTimeout(function() {
console.log(i);
});
}
for (var i = 0; i < 2; i++) {
sendRequest(i);
}
}
test2();
// 1
// 2
これに関して特別なのは、「プリミティブは値渡しされてコピーされるため、クロージャが定義されると、前のループの値を保持する」ということです。
COUNTER BE AN ANJECT
クロージャは参照を介して親関数変数にアクセスできるため、このアプローチはプリミティブのアプローチとは異なります。
// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
var index = { i: 0 };
for (index.i=0; index.i<2; index.i++) {
setTimeout(function() {
console.log('test3: ' + index.i);
});
}
}
test3();
// 2
// 2
そのため、オブジェクトとして渡される変数に対してクロージャが作成されても、ループインデックスの値は保持されません。これは、オブジェクトの値はコピーされないのに対して、参照によってアクセスされることを示すためです。
function test4() {
var index = { i: 0 };
function sendRequest(index, i) {
setTimeout(function() {
console.log('index: ' + index);
console.log('i: ' + i);
console.log(index[i]);
});
}
for (index.i=0; index.i<2; index.i++) {
sendRequest(index, index.i);
}
}
test4();
// index: { i: 2}
// 0
// undefined
// index: { i: 2}
// 1
// undefined
これは、 'クロージャ'と 'ノンクロージャ'がどのように機能するかに関して、醜いJavaScriptがいかに醜いかを証明しています。
の場合:
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
funcs [i]はグローバル関数で、 'console.log( "My value:" + 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);
}
このjavascriptのクロージャデザインのため、 'console.log( "My value:" + i);'外部関数 'createfunc(i)'からiを出力しています
cプログラミング言語がしているように、JavaScriptは関数内の 'static'変数のようなまともなものを設計できないからです。
ES5までは、この問題は closing を使用してのみ解決できます。
しかしES6では、ブロックレベルのスコープ変数があります。 var から let を最初の for loop に変更すると問題は解決します。
var funcs = [];
for (let i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
ES6
をサポートしているので、これに対する最善の方法は、このような状況でlet
およびconst
キーワードを使用することです。そのため、var
変数はhoisted
を取得し、ループの終わりですべてのi
...に対してclosures
の値が更新されるので、ループスコープ変数を設定するにはlet
を使用するだけです。
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
new Function
を活用しましょう。したがって、i
は closure の変数でなくなり、テキストの一部になります。
var funcs = [];
for (var i = 0; i < 3; i++) {
var functionBody = 'console.log("My value: ' + i + '");';
funcs[i] = new Function(functionBody);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Es6を使わないとしましょう。あなたはIFFY関数を使用することができます:
var funcs = [];
for (var i = 0; i < 13; i++) {
funcs[i] = (function(x) {
console.log("My value: " + i)})(i);}
しかしそれは違うでしょう。
この質問は古くなって回答されていますが、もう1つの非常に興味深い解決策があります。
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i]();
}
その変化は非常に小さいので、私がしたことを見るのはほとんど困難です。 2番目の反復子をa jからiに切り替えました。これはどういうわけかあなたに望ましい結果を与えるのに間に合うようにiの状態を更新します。私はこれを偶然にしましたが、以前の答えを考慮すると意味があります。
私はこの小さいながらも非常に重要な違いを指摘するためにこれを書きました。それが私のような他の学習者のための混乱を解消するのに役立つことを願っています。
注:正しい答えだと思うので、これは共有しません。これは、特定の状況下ではおそらく壊れる可能性のあるフレークソリューションです。実際、本当にうまくいったことに私はとても驚いています。
asyncIterable = [1,2,3,4,5,6,7,8];
(async function() {
for await (let num of asyncIterable) {
console.log(num);
}
})();
OK。私は全ての答えを読みました。ここには良い説明がありますが - これをうまく動作させることはできませんでした。だから私はインターネットを見に行きました。 https://dzone.com/articles/why-does-javascript-loop-only-use-last-value の人には、ここには記載されていない回答がありました。だから私は私が私が簡単な例を投稿しようと思った。これは私にとってずっと理にかなっています。
長所と短所は、LETコマンドがNiceであることですが、現在使用されているだけです。しかし、LETコマンドは本当にTRY-CATCHコンボです。これはIE3にまでさかのぼります(私は信じています)。 TRY-CATCHコンボを使う - 人生はシンプルで良いです。おそらくEMCScriptの人々がなぜそれを使うことにしたのでしょう。 setTimeout()関数も必要ありません。だから時間は失われません。基本的には、FORループごとに1つのTRY-CATCHコンボが必要です。これが一例です。
for( var i in myArray ){
try{ throw i }
catch(ii){
// Do whatever it is you want to do with ii
}
}
複数のFORループがある場合は、それぞれにTRY-CATCHコンボを追加するだけです。また、個人的には、使用しているすべてのFOR変数のうち2つの文字を常に使用します。だから "i"の "ii"など。この手法をルーチン内で使用して、マウスオーバーコマンドを別のルーチンに送信します。