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

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

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

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

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

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

グッドジョブ、ESPN

グッドジョブ、ESPN

今日のホワイトハウスの記者会見で、報道官のサラ・ハッカビー・サンダースは、スポーツセンターのホストであるイェメル・ヒルのドナルド・トランプに関する最近のツイートについてコメントするよう求められ、大統領と彼の政策を白人至上主義者として説明した。ヒルは彼女のつぶやきのためにESPNによって公に叱責されました。

アマゾンからのこのレミントンツールセールで髪を整える、スタイルを整える、乾かす、または取り除く

アマゾンからのこのレミントンツールセールで髪を整える、スタイルを整える、乾かす、または取り除く

基本的に頭のあらゆる部分から髪の毛を整えたり、ブロードライしたり、まっすぐにしたり、脱毛したりする必要がある場合は、このレミントンゴールドボックスが最適です。今日だけ、Amazonは、すでに人気のあるShortcut Pro Self-HaircutKitやPearlPro Ceramic Flat Ironのように、グルーミングをはるかに簡単にするヘアツールをマークダウンしています。

カナダの元桂冠詩人が、史上最高の文化の盗用でトゥパックとマヤアンジェロウから盗んだ

カナダの元桂冠詩人が、史上最高の文化の盗用でトゥパックとマヤアンジェロウから盗んだ

トゥパックシャクール(ティムモーゼンフェルダー/ゲッティイメージズ); マヤアンジェロウ(マーティンゴッドウィン/ゲッティイメージズ)移動、テイラースウィフト。ケンドールとカイリーは、必要な数の座席を持っています。

テスラは、ハリケーンイルマによる避難を容易にするために、フロリダでの車両の範囲を拡大しています

テスラは、ハリケーンイルマによる避難を容易にするために、フロリダでの車両の範囲を拡大しています

写真:Tesla Motorsフロリダに住んでいて、Tesla Model S、Model X 60、またはModel 60Dを使用している場合、車の自律性は50 km(約30マイル)高くなります。これは失敗ではなく、ハリケーンイルマによる避難作業を容易にするために会社自身が命じた自治権の一時的な延長です。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

投資ノート:Bioscout AU$300万シード

投資ノート:Bioscout AU$300万シード

Bioscoutは、農家を運転席に置くという使命を負っています。Artesian(GrainInnovate)やUniseedと並んで、最新のシードラウンドでチームを支援できることをうれしく思います。問題真菌症による重大な作物の損失は、農民にとって試練であることが証明されています。

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

遠隔医療は、パンデミック後の時代では新しいものではなく、時代遅れの分野でもありません。しかし、業界を詳しく見ると、需要と供給の強力な持続可能性と、米国で絶え間ない革命となる強力な潜在的成長曲線を示しています。

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

2021年は、世界的なベンチャーキャピタル(VC)の資金調達にとって記録的な年でした。DealStreetAsiaによると、東南アジアも例外ではなく、この地域では年間で記録的な25の新しいユニコーンが採掘されました。

ムーアの法則を超えて

ムーアの法則を超えて

計算に対する私たちの欲求とムーアの法則が提供できるものとの間には、指数関数的に増大するギャップがあります。私たちの文明は計算に基づいています—建築と想像力の現在の限界を超える技術を見つけなければなりません。

Language