Now I am going through self-study on one of the online resources on JavaScript. In addition to theory, it also has puzzles. So, in one of the tasks in the topic "Visibility. Closures" I faced a complete misunderstanding of the solution of the problem that the author proposes (I had only one solution, and I will not consider it - below is the problem with the code and examples from the author) .

The task:

The following code creates an array of shooter-function shooters. According to the plan, each shooter must display his number:

function makeArmy() { var shooters = []; for (var i = 0; i < 10; i++) { var shooter = function() { // функция-стрелок alert( i ); // выводит свой номер }; shooters.push(shooter); } return shooters; } var army = makeArmy(); army[0](); // стрелок выводит 10, а должен 0 army[5](); // стрелок выводит 10... // .. все стрелки выводят 10 вместо 0,1,2...9 

Why do all shooters display the same thing? Correct the code so that the arrows work as intended. Offer several fixes. (Question of the author of the task)

Suggested solutions by the author that are not clear to me:

(1) Use the additional function to “catch” the current value of i:

 function makeArmy() { var shooters = []; for (var i = 0; i < 10; i++) { var shooter = (function(x) { return function() { alert( x ); }; })(i); shooters.push(shooter); } return shooters; } var army = makeArmy(); army[0](); // 0 army[1](); // 1 

I did not understand this solution to the task. What do these second brackets with i give in this variant and why in JavaScript, unlike other normal languages ​​like Java x = i, although the names of the parameters are different. In other words, I do not understand how the i is caught.

(2) Wrap the entire cycle in a temporary function:

 function makeArmy() { var shooters = []; for (var i = 0; i < 10; i++)(function(i) { var shooter = function() { alert( i ); }; shooters.push(shooter); })(i); return shooters; } var army = makeArmy(); army[0](); // 0 army[1](); // 1 

This solution to the task introduces me even more into a stupor. Where is the i value of each arrow generally stored? What role do the second brackets with the parameter (i) play in all this?

  • one
    and the problem itself is understood why it occurs? - Grundy
  • Yes, I understood the problem, Grundy - IngeniousTom

2 answers 2

The problem with the variable is due to the closure of the function on the context, that is, on the external variable i . This can be easily determined by deducing access to this variable to external code. I will use console output instead of a pop-up message.

 function makeArmy() { var shooters = []; for (makeArmy.i = 0; makeArmy.i < 10; makeArmy.i++) { var shooter = function() { console.log( makeArmy.i ); }; shooters.push(shooter); } return shooters; } 

The dependency is now clearly visible and that the function outputs the variable to which it is bound during the call. And by the time of the call, the cycle has completely passed and the variable is equal to the final value of 10 .

 var shooters = makeArmy(); a[0](); //выведет 10 makeArmy.i = 20; a[0](); //выведет 20 

Accordingly, for the normal formation of the function, it is necessary to break this connection and copy the current number inside the loop.

[Decision]

This can be done through an additional function that copies the value; I will simplify an example for this:

 function test(){ var i = 0; result = function(){ console.log(i); }; i = 10; return result; } test()(); //test() возвращает функцию, вторые скобки для мгновенного вызова вернувшейся функции 

We need to make it so that the variable is not closed, and copied. One way is to pass a variable as a parameter to a function, in which case it will be copied, not closed. And inside we will form the function or object we need into this copied variable.

 function test(){ var i = 0; var makeResult = function(x){ return function(){ console.log(x); }; } var result = makeResult(i); // вызываем функцию и передаем ей параметр который скопируется внутри и из него сформируется нужная функция. i = 10; return result; } test()(); //выводит 0 

Thus, we bypassed the closure to the external context by copying when called with the parameter. In fact, this can be simplified by immediately calling the function makeResult .

 function test(){ var i = 0; var result = function(x){ return function(){ console.log(x); }; }(i); //сразу вызываем i = 10; return result; } test()(); //выводит 0 

The same goes for the cycle with the soldiers, we made an intermediate function that formed the necessary one and returned it as the variable i increased.

 function test(){ var results = []; for(var i = 0; i < 10; i++){ var result = function(x){ return function(){ console.log(x); }; }(i); results.push(result); } return results; } test()[5](); 

What is in the first, that in the second examples, the same thing is done — an intermediate function is called with a parameter for copying and breaking the context. Only in one the objective function is formed, and in the second the whole object is entirely.

  • How i gets into x in the function (x) expression I understood. But where is the current x stored after that? What does function (x) leave in its place after the call? The variable x + code of the internal function, the bark of this x is then substituted when called? Or only the code of the internal function with the already substituted value? And maybe LexicalEnvironment or he does not matter? - IngeniousTom
  • one
    @IngeniousTom, the variable x as a parameter is stored in the internal function stack (in other languages, as a rule, the same). After calling function(x) , the code of the internal function + which refers to this x on the stack is returned, so it is not destroyed after the function ends as expected (this is called a closure). Therefore, again, a closure is obtained, but already on a copy in the function function(x) stack, and not on an external variable i which changes. - Alex Krass
  • Thanks - appreciated. That is, I put you +1 - IngeniousTom

The problem arises here:

  for (var i = 0; i < 10; i++) { var shooter = function() { alert( i ); }; shooters.push(shooter); } 

Namely, when you call:

 army[0](); army[5](); 

The first solution is simple, because when called, the function returned the final value of the cycle (10). By wrapping it in a function, we make the value i written and returned every time.

And wrapped in a temporary function, the function will be executed i times.