ループ内のJavaScriptクロージャ–簡単な実用例

2913
nickf 2009-04-15 20:06.

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>

…または非同期コード、たとえばPromisesの使用:

// 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 infor 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();
}

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

30 answers

2214
harto 2009-04-15 20:18.

問題は、i各無名関数内の変数が、関数外の同じ変数にバインドされていることです。

ES6ソリューション: let

ECMAScriptの6(ES6)新発売letconst違ったよりもスコープされるキーワード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毎回新しいものを作成しないため、上記のすべての関数は、使用した場合と同じように3をログに記録しますvar)。エッジ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」の値が意図したとおりに維持されるようにします。

390
Bjorn 2009-04-15 20:10.

試してみてください:

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の使用に関する最近の回答.bindが、この種のことを今行うための最良の方法だと思います。's_.partialをいじる必要がない、またはいじりたくない場合は、lo-dash / underscoreもあります。bindthisArg

357
Aust 2013-10-12 06:41.

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

更新

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

275
neurosnap 2013-10-12 08:23.

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

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内の非同期機能で使用するために保存されます。

170
woojoo666 2015-04-10 23:57.

パーティーに少し遅れましたが、今日この問題を調査していて、回答の多くが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]();
}

今ではとても簡単です!詳細については、私の情報の基になっているこの回答を参照してください。

157
Ben McCormick 2013-05-21 17:04.

ES6が広くサポートされるようになったため、この質問に対する最良の答えが変わりました。ES6は、この正確な状況に対応するキーワードletconstキーワードを提供します。クロージャをいじくり回す代わりに、次の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/

ここのドキュメント:

  • const
  • しましょう

ただし、Edge 14より前のIE9-IE11とEdgeはサポートしますletが、上記は間違っていることに注意してください(i毎回新しいものを作成しないため、上記のすべての関数は、使用した場合と同じように3をログに記録しますvar)。エッジ14はついにそれを正しくします。

91
Darren Clark 2009-04-15 20:48.

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

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

他のほとんどの回答は、値を変更しない別の変数を作成することで回避する方法を提供します。

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

74
eglasius 2009-04-15 20:25.

あなたが理解する必要があるのは、javascriptの変数のスコープが関数に基づいているということです。これは、ブロックスコープがあるc#と言うのとは重要な違いであり、変数をfor内の変数にコピーするだけで機能します。

変数に関数スコープがあるので、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]();
}

63
Boann 2012-08-07 22:45.

ビョルン(apphacker)に似た、この手法の別のバリエーションを次に示します。これにより、変数値をパラメーターとして渡すのではなく、関数内で割り当てることができます。これは、より明確な場合があります。

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

どの手法を使用する場合でも、index変数は一種の静的変数になり、内部関数の返されたコピーにバインドされることに注意してください。つまり、その値の変更は呼び出し間で保持されます。とても便利です。

58
Noname 2013-04-20 23:59.

これは、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]は独立していないことに注意してください。実際、それらは同じように動作し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);
  }
}

これが機能するのは、関数スコープ内のローカル変数と関数引数変数に、入力時に新しいコピーが割り当てられるためです。

51
Kemal Dağ 2013-06-26 04:21.

最も簡単な解決策は、

使用する代わりに:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

これは「2」を3回警告します。これは、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(Immediately-Invoked Function Expression)でカプセル化new_iし、パラメーターとして渡し、それをとしてキャプチャすることiです。匿名関数はすぐに実行されるため、匿名関数i内で定義された関数ごとに値が異なります。

この解決策は、この問題に悩まされている元のコードへの最小限の変更を必要とするため、このような問題に適合するようです。実際、これは仕様によるものであり、まったく問題にはならないはずです。

33
Daryl 2014-05-03 17:42.

を使用する簡単なソリューションは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
32
yilmazburk 2013-09-20 04:20.

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

  • 配列なし

  • 余分なforループはありません


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

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

http://jsfiddle.net/7P6EN/

29
Travis J 2014-03-06 13:03.

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

The error actually does not occur until funcs[someIndex] is executed (). Using this same logic, it should be apparent that the value of i is also not collected until this point either. Once the original loop finishes, i++ brings i to the value of 3 which results in the condition i < 3 failing and the loop ending. At this point, i is 3 and so when funcs[someIndex]() is used, and i is evaluated, it is 3 - every time.

To get past this, you must evaluate i as it is encountered. Note that this has already happened in the form of funcs[i] (where there are 3 unique indexes). There are several ways to capture this value. One is to pass it in as a parameter to a function which is shown in several ways already here.

Another option is to construct a function object which will be able to close over the variable. That can be accomplished thusly

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
24
Costa 2016-12-08 07:33.

JavaScript functions "close over" the scope they have access to upon declaration, and retain access to that scope even as variables in that scope change.

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

Each function in the array above closes over the global scope (global, simply because that happens to be the scope they're declared in).

Later those functions are invoked logging the most current value of i in the global scope. That's the magic, and frustration, of closure.

"JavaScript Functions close over the scope they are declared in, and retain access to that scope even as variable values inside of that scope change."

Using let instead of var solves this by creating a new scope each time the for loop runs, creating a separated scope for each function to close over. Various other techniques do the same thing with extra functions.

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 makes variables block scoped. Blocks are denoted by curly braces, but in the case of the for loop the initialization variable, i in our case, is considered to be declared in the braces.)

15
wpding 2014-07-15 04:42.

After reading through various solutions, I'd like to add that the reason those solutions work is to rely on the concept of scope chain. It's the way JavaScript resolve a variable during execution.

  • Each function definition forms a scope consisting of all the local variables declared by var and its arguments.
  • If we have inner function defined inside another (outer) function, this forms a chain, and will be used during execution
  • When a function gets executed, the runtime evaluates variables by searching the scope chain. If a variable can be found in a certain point of the chain it will stop searching and use it, otherwise it continues until the global scope reached which belongs to window.

In the initial code:

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

When funcs gets executed, the scope chain will be function inner -> global. Since the variable i cannot be found in function inner (neither declared using var nor passed as arguments), it continues to search, until the value of i is eventually found in the global scope which is window.i.

By wrapping it in an outer function either explicitly define a helper function like harto did or use an anonymous function like 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

When funcs gets executed, now the scope chain will be function inner -> function outer. This time i can be found in the outer function's scope which is executed 3 times in the for loop, each time has value i bound correctly. It won't use the value of window.i when inner executed.

More detail can be found here
It includes the common mistake in creating closure in the loop as what we have here, as well as why we need closure and the performance consideration.

13
Prithvi Uppalapati 2016-11-08 01:25.

With new features of ES6 block level scoping is managed:

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
}

The code in OP's question is replaced with let instead of var.

10
Christian Landgren 2014-12-10 12:24.

I'm surprised no one yet has suggested using the forEach function to better avoid (re)using local variables. In fact, I'm not using for(var i ...) at all anymore for this reason.

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

// edited to use forEach instead of map.

9
jottos 2009-04-15 20:18.

The reason your original example did not work is that all the closures you created in the loop referenced the same frame. In effect, having 3 methods on one object with only a single i variable. They all printed out the same value.

9
sidhuko 2018-01-14 03:17.

This question really shows the history of JavaScript! Now we can avoid block scoping with arrow functions and handle loops directly from DOM nodes using Object methods.

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
Ali Kahoot 2016-11-04 21:46.

First of all, understand what's wrong with this code:

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
}

Here when the funcs[] array is being initialized, i is being incremented, the funcs array is initialized and the size of func array becomes 3, so i = 3,. Now when the funcs[j]() is called, it is again using the variable i, which has already been incremented to 3.

Now to solve this, we have many options. Below are two of them:

  1. We can initialize i with let or initialize a new variable index with let and make it equal to i. So when the call is being made, index will be used and its scope will end after initialization. And for calling, index will be initialized again:

    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. Other Option can be to introduce a tempFunc which returns the actual function:

    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]();                        
    }
    
8
Vikash Singh 2017-01-21 00:02.

Use closure structure, this would reduce your extra for loop. You can do it in a single for loop:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
7
Bimal Das 2018-01-17 04:29.

We will check , what actually happens when you declare var and let one by one.

Case1 : using var

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

Now open your chrome console window by pressing F12 and refresh the page. Expend every 3 functions inside the array.You will see an property called [[Scopes]].Expand that one. You will see one array object called "Global",expand that one. You will find a property 'i' declared into the object which having value 3.

Conclusion:

  1. When you declare a variable using 'var' outside a function ,it becomes global variable(you can check by typing i or window.i in console window.It will return 3).
  2. The annominous function you declared will not call and check the value inside the function unless you invoke the functions.
  3. When you invoke the function , console.log("My value: " + i) takes the value from its Global object and display the result.

CASE2 : using let

Now replace the 'var' with 'let'

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

Do the same thing, Go to the scopes . Now you will see two objects "Block" and "Global". Now expand Block object , you will see 'i' is defined there , and the strange thing is that , for every functions , the value if i is different (0 , 1, 2).

Conclusion:

When you declare variable using 'let' even outside the function but inside the loop , this variable will not be a Global variable , it will become a Block level variable which is only available for the same function only.That is the reason , we are getting value of i different for each function when we invoke the functions.

For more detail about how closer works , please go through the awesome video tutorial https://youtu.be/71AtaJpJHw0

4
Rune FS 2015-06-18 02:02.

You could use a declarative module for lists of data such as query-js(*). In these situations I personally find a declarative approach less surprising

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

You could then use your second loop and get the expected result or you could do

funcs.iterate(function(f){ f(); });

(*) I'm the author of query-js and therefor biased towards using it, so don't take my words as a recommendation for said library only for the declarative approach :)

4
Rax Wunter 2015-12-18 04:14.

I prefer to use forEach function, which has its own closure with creating a pseudo range:

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
}

That looks uglier than ranges in other languages, but IMHO less monstrous than other solutions.

4
pixel 67 2016-05-06 01:48.

And yet another solution: instead of creating another loop, just bind the this to the return function.

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
}

By binding this, solves the problem as well.

4
Shivang Gupta 2019-05-03 01:57.

Till ES5, This problem can only be solved using closure.

But now in ES6, we have block level scope variables. Changing var to let in first for loop will solve the problem.

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
}

3
Pawel 2017-03-01 05:09.

Many solutions seem correct but they don't mention it's called Currying which is a functional programming design pattern for situations like here. 3-10 times faster than bind depending on the browser.

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

See the performance gain in different browsers.

3
Buksy 2016-11-04 22:58.

Your code doesn't work, because what it does is:

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?

Now the question is, what is the value of variable i when the function is called? Because the first loop is created with the condition of i < 3, it stops immediately when the condition is false, so it is i = 3.

You need to understand that, in time when your functions are created, none of their code is executed, it is only saved for later. And so when they are called later, the interpreter executes them and asks: "What is the current value of i?"

So, your goal is to first save the value of i to function and only after that save the function to funcs. This could be done for example this way:

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
}

This way, each function will have it's own variable x and we set this x to the value of i in each iteration.

This is only one of the multiple ways to solve this problem.

3
Ashish Yadav 2018-07-13 22:02.
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
}

Related questions

MORE COOL STUFF

「水曜日」シーズン1の中心には大きなミステリーがあります

「水曜日」シーズン1の中心には大きなミステリーがあります

Netflixの「水曜日」は、典型的な10代のドラマ以上のものであり、実際、シーズン1にはその中心に大きなミステリーがあります.

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ケイト・ミドルトンは、州の夕食会と州の訪問中にカミラ・パーカー・ボウルズからスポットライトを奪いたくなかった、と専門家は言う.

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンが、オリビア・ワイルドが彼女とハリー・スタイルズとの間の「難しい」が「非常に友好的」な分割を恒久的にすることを望んでいる理由を見つけてください.

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする 

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする&nbsp;

エリザベス女王の死後、ケイト・ミドルトンが舞台裏で「非常に困難な時期」を過ごしていたと伝えられている理由を調べてください.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

Total War:Warhammer:Kotakuレビュー

Total War:Warhammer:Kotakuレビュー

私はこのゲームを嫌う準備ができていました。先週の前に、Total War:Warhammerについての私の考えがありました:それでもここに私は、私の手にある完成品であり、私は変わった男です。

涙の道:軍事化された帝国主義勢力がスタンディングロックキャンプを占領

涙の道:軍事化された帝国主義勢力がスタンディングロックキャンプを占領

スタンディングロックスー族のメンバーと水の保護者は、ノースダコタ州のスタンディングロックにあるオセティサコウィンキャンプを去ります。(Twitter経由のCNNスクリーンショット)火と煙がスカイラインを覆い、スタンディングロックスー族のメンバーと水の保護者が、聖なるものを守りながら建てた家、オセティサコウィン(セブンカウンシルファイアーズ)キャンプから行進し、太鼓を打ち、歌い、祈りました。ダコタアクセスパイプラインとしても知られる「ブラックスネーク」からの土地。

シアーズとKマートはイヴァンカ・トランプの商品を自分たちで取り除いています

シアーズとKマートはイヴァンカ・トランプの商品を自分たちで取り除いています

写真:APシアーズとKマートは、イヴァンカ・トランプのトランプホームアイテムのコレクションも、誰も購入したくないために削除しました。シアーズとKマートの両方の親会社であるシアーズホールディングスは、土曜日のABCニュースへの声明で、彼らが気にかけていると辛抱強く説明しましたトランプラインを売り続けるにはお金を稼ぐことについてあまりにも多く。

ポテトチップスでたった10分でスペインのトルティーヤを作る

ポテトチップスでたった10分でスペインのトルティーヤを作る

伝統的なスペインのトルティーヤは通常、オリーブオイルで柔らかくなるまで調理されたポテトから始まります(30分以上かかる場合があります)が、ケトルで調理されたポテトチップスの助けを借りてわずか10分でテーブルに置くことができます。上のビデオはすべてがバラバラにならないように裏返す方法を含め、レシピ全体を説明しますが、必要なのは4〜5個の卵と3カップのケトルチップスだけです。

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、子供向けのパズルの本の序文を書き、ジョージ王子、シャーロット王女、ルイ王子と一緒にテキストを読むと述べた.

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

Yak's Produce は、数十個のつぶれたメロンを野生動物のリハビリ専門家であるレスリー グリーンと彼女のルイジアナ州の救助施設で暮らす 42 匹の動物に寄付しました。

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

8 枚目のスタジオ アルバムのリリースに向けて準備を進めているデミ ロヴァートは、「スーパー グレート ガイ」と付き合っている、と情報筋は PEOPLE に確認しています。

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

イーサン プラスの誕生日のお祝いは、TLC のウェルカム トゥ プラスビルのシーズン 4 のフィナーレで、戦争中の母親のキム プラスと妻のオリビア プラスを結びつけました。

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

ホームオフィスのセットアップ術を極めよう!AppExert の開発者は、家族全員が一緒にいる場合でも、在宅勤務の技術を習得しています。祖父や曽祖父が共同家族で暮らしていた頃の記憶がよみがえりました。

2022 年、私たちのデジタル ライフはどこで終わり、「リアル ライフ」はどこから始まるのでしょうか?

20 年前のタイムトラベラーでさえ、日常生活におけるデジタルおよびインターネットベースのサービスの重要性に驚くことでしょう。MySpace、eBay、Napster などのプラットフォームは、高速化に焦点を合わせた世界がどのようなものになるかを示してくれました。

ニューロマーケティングの秘密科学

ニューロマーケティングの秘密科学

マーケティング担当者が人間の欲望を操作するために使用する、最先端の (気味が悪いと言う人もいます) メソッドを探ります。カートをいっぱいにして 3 桁の領収書を持って店を出る前に、ほんの数点の商品を買いに行ったことはありませんか? あなたは一人じゃない。

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

私たちが住んでいる世界を確実に理解するには、データが必要です。ただし、空間参照がない場合、このデータは地理的コンテキストがないと役に立たなくなる可能性があります。

Language