ループ内のJavaScriptクロージャ–簡単な実用例
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 in
、for 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();
}
この基本的な問題の解決策は何ですか?
回答
問題は、i
各無名関数内の変数が、関数外の同じ変数にバインドされていることです。
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
毎回新しいものを作成しないため、上記のすべての関数は、使用した場合と同じように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」の値が意図したとおりに維持されるようにします。
試してみてください:
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もあります。bind
thisArg
まだ言及されていない別の方法は、 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]();
}
即時呼び出し関数式を使用して、インデックス変数を囲む最も簡単で読みやすい方法:
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/
ここのドキュメント:
- const
- しましょう
ただし、Edge 14より前のIE9-IE11とEdgeはサポートしますlet
が、上記は間違っていることに注意してください(i
毎回新しいものを作成しないため、上記のすべての関数は、使用した場合と同じように3をログに記録しますvar
)。エッジ14はついにそれを正しくします。
別の言い方をすれば、i
関数内は、関数の作成時ではなく、関数の実行時にバインドされるということです。
クロージャを作成するときi
は、外部スコープで定義された変数への参照であり、クロージャを作成したときのようにそのコピーではありません。実行時に評価されます。
他のほとんどの回答は、値を変更しない別の変数を作成することで回避する方法を提供します。
わかりやすくするために説明を追加したいと思いました。解決策として、個人的には、ここでの回答からそれを行う最も自明な方法であるHarto'sを使用します。投稿されたコードはどれでも機能しますが、新しい変数を宣言する理由(Freddyと1800)や奇妙な埋め込みクロージャ構文(apphacker)を説明するコメントの山を書く必要はなく、クロージャファクトリを選択します。
あなたが理解する必要があるのは、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]();
}
ビョルン(apphacker)に似た、この手法の別のバリエーションを次に示します。これにより、変数値をパラメーターとして渡すのではなく、関数内で割り当てることができます。これは、より明確な場合があります。
var funcs = [];
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]
は独立していないことに注意してください。実際、それらは同じように動作し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);
}
}
これが機能するのは、関数スコープ内のローカル変数と関数引数変数に、入力時に新しいコピーが割り当てられるためです。
最も簡単な解決策は、
使用する代わりに:
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
内で定義された関数ごとに値が異なります。
この解決策は、この問題に悩まされている元のコードへの最小限の変更を必要とするため、このような問題に適合するようです。実際、これは仕様によるものであり、まったく問題にはならないはずです。
を使用する簡単なソリューションは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
この短いものを試してください
配列なし
余分なforループはありません
for (var i = 0; i < 3; i++) {
createfunc(i)();
}
function createfunc(i) {
return function(){console.log("My value: " + i);};
}
http://jsfiddle.net/7P6EN/
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);
};
};
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.)
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 itsarguments
. - 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.
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
.
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.
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.
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>
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:
We can initialize
i
withlet
or initialize a new variableindex
withlet
and make it equal toi
. 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](); }
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](); }
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);
}
We will check , what actually happens when you declare
var
andlet
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:
- When you declare a variable using
'var'
outside a function ,it becomes global variable(you can check by typingi
orwindow.i
in console window.It will return 3). - The annominous function you declared will not call and check the value inside the function unless you invoke the functions.
- When you invoke the function ,
console.log("My value: " + i)
takes the value from itsGlobal
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
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 :)
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.
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.
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
}
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.
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.
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
}