The variable in the generated JavaScript function does not behave as expected

I'm having some problems with the scope of JavaScript when generating a function from a loop.

What I want:

How I want this to work is the for loop, which generates a function called doStuff + i for each iteration. For example, the first iteration will generate doStuff1() , the second will generate doStuff2() , etc. Etc. The functions themselves (for example) just need to print i & mdash, that is, doStuff1() prints 1 , doStuff2() prints 2 , etc.

What I get:

What actually happens, i do not “insert” into the function. It becomes part of a global scale or something like 10 for each function. You can see this if you click the first button in the snippet below.

What I tried:

Using the generator function

In Example Two, I tried using function* notation to create the correct function generator. I am pretty sure that I implemented it before, but I never used this notation, so I could leave. Please let me know if this happens.

The result is the same as in example 2.

Using strings instead of integers

For example, three, I decided to try using a string instead of an integer, and it works! For each iteration, a added to the line, so when I run the generated functions in order, I get a beautiful little pyramid of the letter a .

Declaring and assigning variables elsewhere

Since I had to define the stringOut variable in another area in Example 3, I decided to try the same with numbers, for example 4, and it works again! This does not matter much to me, since in a higher volume it seems that he will be more prone to the same problems as examples 1 and 2.

What I want to know:

  • How do examples 3 and 4 work, but 1 and 2 do not work? Edit: my code was broken. None of this works.
  • Did I use the function* generator declaration correctly?
  • What is the best (easiest, concise and readable) way to do this? I know that creating an array of functions is likely to be better, but in this situation this is not possible.

Code example:

 function test1() { document.getElementById("output").innerHTML = "#1 Output:"; var myFunctions = []; for (var i = 0; i < 10; i++) { myFunctions[i] = function() { document.getElementById("output").innerHTML += "<br>" + i; } } for (var j = 0; j < 10; j++) { myFunctions[j](); } } function test2() { document.getElementById("output").innerHTML = "#2 Output:"; window.test2Funcs = {}; function* Generator() { var functionName = "doStuff"; var number = 0; while (number < 10) { number++; yield { myFunction: function() { document.getElementById("output").innerHTML += "<br>" + number; }, name: functionName + (number) } } } var generator = new Generator(); for (var k = 0; k < 10; k++) { var out = generator.next().value; window.test2Funcs[out.name] = out.myFunction; } for (var l = 1; l < 11; l++) { func = "doStuff" + l; test2Funcs[func](); } } function test3() { document.getElementById("output").innerHTML = "#3 Output:"; var myFunctions = []; var stringOut = ""; for (var i = 0; i < 10; i++) { stringOut += "a"; // Edit. Moved from within function below. myFunctions[i] = function() { document.getElementById("output").innerHTML += "<br>" + stringOut; } } for (var j = 0; j < 10; j++) { myFunctions[j](); } } function test4() { document.getElementById("output").innerHTML = "#4 Output:"; var myFunctions = []; var numOut = 0; // Edit. Used to be var numOut = ""; for (var i = 0; i < 10; i++) { numOut++; // Edit. Moved from within function below. myFunctions[i] = function() { document.getElementById("output").innerHTML += "<br>" + numOut; } } for (var j = 0; j < 10; j++) { myFunctions[j](); } } document.getElementById("test1").addEventListener("click", test1); document.getElementById("test2").addEventListener("click", test2); document.getElementById("test3").addEventListener("click", test3); document.getElementById("test4").addEventListener("click", test4); 
 <button id="test1">1st Attempt</button> <button id="test2">2nd Attempt</button> <button id="test3">3rd Attempt</button> <button id="test4">4th Attempt</button> <div id="output"> Output: </div> 
+6
source share
1 answer

The problem is that you are not creating multiple functions in a loop, they are all the same with the function instance, sharing the same closure (which includes i ). By the time functions i are called, this is the value that it had when exiting the loop.

 var funcs = []; for (var i=0; i < 5; i++) { funcs.push(function() { console.log(i); }); } // Here, i is 5 funcs[0](); // 5 funcs[1](); // 5 funcs[4](); // 5 

How can you fix this? By adding extra closure, I call this technique “freezing closure”

 function createHandler(val) { return function() { console.log(val); } } var funcs = []; for (var i = 0; i < 5; i++) { // By calling a different function, a copy of i is passed to that function funcs.push(createHandler(i)); } // Here, i is 5 funcs[0](); // 0 funcs[1](); // 1 funcs[4](); // 4 

You can also use Function.bind

 var funcs = []; for (var i = 0; i < 5; i++) { // By calling bind, a new function is created, with a new closure funcs.push( (function(val) { console.log(val); }).bind(null, i) ); } // Here, i is 5 funcs[0](); // 0 funcs[1](); // 1 funcs[4](); // 4 
+2
source

All Articles