web-dev-qa-db-ja.com

ループ内の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.
  };
}
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.
}

この基本的な問題に対する解決策は何ですか?

2575
nickf

問題は、各無名関数内の変数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"の値が残ることを保証します。


2015年ソリューション: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()関数はあなたに同様の能力を与えます。


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);
    };
}

ただし、Edge 14より前のIE9-IE11およびEdgeはletをサポートしていますが、間違った方法で取得します(毎回新しいiを作成するわけではないため、varを使用した場合のように上記のすべての関数は3を記録します)。エッジ14はついにそれを正しくします。

1980
harto

試してください:

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の使用に関する最近の回答 が、このようなことをする最善の方法だと思います。あなたがbindthisArgを必要としない、あるいは混乱させたくないときは、ローダッシュ/アンダースコアの_.partialもあります。

361
Bjorn Tipling

まだ言及されていない別の方法は 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]();
}
333
Aust

即時呼び出し関数式 を使用して、インデックス変数を囲む最も簡単で読みやすい方法を示します。

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);

}

これはイテレータiindexとして定義している無名関数に送ります。これによりクロージャーが作成され、変数iは後でIIFE内の非同期機能で使用するために保存されます。

252
neurosnap

パーティーには少々時間がかかりましたが、私は今日この問題を調査していて、多くの答えが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では、内部スコープが外部スコープを「囲む」ので内部関数が外部変数にアクセスするという考えです。

Closures

これはまた、内部関数が外部変数を「保持」し、外部関数が戻ってもそれらを有効に保つことを意味します。これを利用するために、ラッパー関数を作成して純粋に新しいスコープを作成し、その新しいスコープ内で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]();
}

それが今どれほど簡単か見てください!詳細については この回答 を参照してください。これは私の情報に基づいています。

149
woojoo666

ES6は現在広くサポートされているので、この質問に対する最良の答えは変わりました。このような状況のために、ES6ではletキーワードとconstキーワードが用意されています。クロージャをいじるのではなく、letを使用してループスコープ変数を次のように設定できます。

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

valは、そのループの特定の順番に固有のオブジェクトを指し、追加のクロージャ表記なしで正しい値を返します。これは明らかにこの問題を著しく単純化します。

constletと似ていますが、最初の代入後に変数名を新しい参照に再バインドできないという追加の制限があります。

最新バージョンのブラウザをターゲットとしているユーザ向けに、ブラウザサポートが追加されました。 const/letは現在、最新のFirefox、Safari、Edge、およびChromeでサポートされています。 Nodeでもサポートされており、Babelのようなビルドツールを利用することでどこでも使用することができます。ここで実用的な例を見ることができます: http://jsfiddle.net/ben336/rbU4t/2/

ドキュメントはこちら:

ただし、IE9〜IE11およびEdge 14より前のEdgeではletをサポートしていますが、上記のように間違っています(毎回新しいiを作成するわけではないため、varを使用した場合のように上記の関数はすべて3を記録します)。エッジ14はついにそれを正しくします。

136
Ben McCormick

別の言い方をすれば、関数内のiは、関数の作成時ではなく、関数の実行時にバインドされるということです。

クロージャを作成するとき、iは外側のスコープで定義された変数への参照であり、クロージャを作成したときのようなコピーではありません。実行時に評価されます。

他の答えのほとんどはあなたのために値を変更しない別の変数を作成することによって回避する方法を提供します。

わかりやすくするために説明を追加したいと思いました。解決策としては、個人的には、ここでの回答からそれを実行するための最も自明の方法であるため、私はHartoのものを使用します。投稿されたコードはどれでも動作しますが、なぜ私は新しい変数を宣言するのか(Freddyと1800年代)、奇妙な埋め込みクロージャ構文(apphacker)を持つのか説明するためにコメントの山を書く必要があります。

83
Darren Clark

あなたが理解する必要があるのは、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]();                        
}
66
eglasius

これはBjorn(apphacker)に似たテクニックの別のバリエーションで、変数として渡すのではなく関数の中に変数値を代入できます。

for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

どのようなテクニックを使用しても、index変数は内部関数の返されたコピーにバインドされた一種の静的変数になります。つまり、値の変更は呼び出し間でも維持されます。とても便利です。

54
Boann

これは、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の新しいコピーも作成されます。したがって、counter1counter2は互いに独立しています。

ループ内のクロージャ

ループ内でクロージャを使用するのは難しいです。

検討してください:

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のクロージャーの落とし穴と使い方 を見てください。

50
Mave

最も簡単な解決策は、

使用する代わりに:

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の値は無名関数内で定義されている関数ごとに異なります。

この問題は、この問題を抱えている元のコードに最小限の変更を加えるだけで済むため、このような問題に適しているようです。実際、これは仕様によるもので、まったく問題にならないはずです。

47
Kemal Dağ

これより短いものを試してください

  • 配列なし

  • ループのための余分なものはありません


for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

http://jsfiddle.net/7P6EN/ /

29
yilmazburk

これは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
24
Daryl

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++i3の値にし、その結果、条件i < 3が失敗し、ループが終了します。この時点では、i3なので、funcs[someIndex]()が使用され、iが評価されると、毎回3になります。

これを乗り越えるためには、iが発生したときにそれを評価する必要があります。これはすでにfuncs[i]の形で行われていることに注意してください(3つのユニークなインデックスがあります)。この価値を捉えるにはいくつかの方法があります。 1つは、すでにいくつかの方法でここに示されている関数にパラメータとして渡すことです。

別の選択肢は、変数を閉じることができるようになる関数オブジェクトを構築することです。それはこうして達成することができます

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
24
Travis J

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は中括弧で宣言されていると見なされます。)

22
Costa

さまざまな解決策を読み終えた後、これらの解決策が機能する理由は 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になります。変数ifunction 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の値は使用されません。

より多くの詳細は見つけることができます ここ
ループにクロージャを作成する際の一般的な間違いや、クロージャが必要な理由とパフォーマンスの考慮事項が含まれます。

13
wpding

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に置き換えられます。

13

ローカル変数の使用を(再)回避するために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を使用するように編集しました。

8

この質問は本当に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>
8
sidhuko

まず最初に、このコードの何が問題なのかを理解してください。

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つです。

  1. iletで初期化するか、新しい変数indexletで初期化して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]();                        
    }
    
  2. 他のオプションは、実際の関数を返す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]();                        
    }
    
7
Ali Kahoot

varletを1つずつ宣言したときに実際に何が起こるかを調べます。

Case1using 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'が見つかります。

enter image description here

enter image description here

結論:

  1. 関数の外側で'var'を使用して変数を宣言すると、それはグローバル変数になります(コンソールウィンドウでiまたはwindow.iを入力して確認できます。3が返されます)。
  2. あなたが宣言した毎年恒例の関数は、あなたが関数を呼び出さない限り、関数内の値を呼び出したりチェックしたりすることはありません。
  3. 関数を呼び出すと、console.log("My value: " + i)はそのGlobalオブジェクトから値を取得して結果を表示します。

CASE2:letを使用

'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)ということがあります。

enter image description here

結論:

関数の外側でもループの内側でも'let'を使用して変数を宣言すると、この変数はグローバル変数にはならず、同じ関数に対してのみ利用可能なBlockレベルの変数になります。関数を呼び出すと、関数ごとにiの値が異なります。

どれだけうまくいくかについての詳細は、素晴らしいビデオチュートリアル https://youtu.be/71AtaJpJHw0 をご覧ください。

7
Bimal Das

あなたのオリジナルの例がうまくいかなかった理由は、あなたがループの中で作成したすべてのクロージャが同じフレームを参照していたからです。実際には、1つのオブジェクトに対して3つのメソッドを持ち、1つのi変数のみを使用します。それらはすべて同じ値を印刷しました。

6
jottos

closure structureを使用すると、余分なforループが少なくなります。単一のforループでそれを行うことができます。

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
5
Vikash Singh
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
}
3
ashish yadav

私は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
}

それは他の言語の範囲よりも酷いように見えますが、私見は他のソリューションよりも怖いものではありません。

3
Rax Wunter

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の作者であり、したがってそれを使うことに偏っているので、宣言的アプローチのためだけにこのライブラリの推奨として私の言葉を使わないでください。

2
Rune FS

あなたのコードは機能しません。

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を持ち、各反復においてこのxiの値に設定します。

これは、この問題を解決するための複数の方法のうちの1つにすぎません。

2
Buksy

多くの解決策は正しいように見えますが、 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);
  }
}

異なるブラウザでのパフォーマンスの向上 を参照してください。

2
Pawel

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]();                      
}
2
B G Hari Prasad

さらに別の解決策:別のループを作成するのではなく、単に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 をバインドすることで、問題も解決します。

2
pixel 67

これは非同期コードでしばしば遭遇する問題で、変数iは可変であり、関数呼び出しが行われた時点でiを使ったコードが実行され、iはその最後の値に変更されます。 loopは クロージャ を作成し、iは3(forループの上限+ 1)に等しくなります。

これを回避するには、反復ごとにiの値を保持し、コピーiを強制する関数を作成します(これはプリミティブなので、役立つ場合はスナップショットと考えてください)。

1
axelduch

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
}
1
Nouman Dilshad

カウンタが原始的

次のようにコールバック関数を定義しましょう。

// ****************************
// 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
1
jsbisht

これは、 'クロージャ'と 'ノンクロージャ'がどのように機能するかに関して、醜い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'変数のようなまともなものを設計できないからです。

0
user1559625

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
}
0
Shivang Gupta

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); 
    };
}
0
Murtaza Hussain

new Function を活用しましょう。したがって、iclosure の変数でなくなり、テキストの一部になります。

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]();
}
0

Es6を使わないとしましょう。あなたはIFFY関数を使用することができます:

var funcs = [];
for (var i = 0; i < 13; i++) {      
funcs[i] = (function(x) {
console.log("My value: " + i)})(i);}

しかしそれは違うでしょう。

0
Eyal Segal

この質問は古くなって回答されていますが、もう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の状態を更新します。私はこれを偶然にしましたが、以前の答えを考慮すると意味があります。

私はこの小さいながらも非常に重要な違いを指摘するためにこれを書きました。それが私のような他の学習者のための混乱を解消するのに役立つことを願っています。

注:正しい答えだと思うので、これは共有しません。これは、特定の状況下ではおそらく壊れる可能性のあるフレークソリューションです。実際、本当にうまくいったことに私はとても驚いています。

0
Brooks DuBois
  asyncIterable = [1,2,3,4,5,6,7,8];

  (async function() {
       for await (let num of asyncIterable) {
         console.log(num);
       }
    })();
0
swogger

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"など。この手法をルーチン内で使用して、マウスオーバーコマンドを別のルーチンに送信します。

0
Mark Manning