Closure and For-Loops

Closure and For-Loops
0.0 0

#1

Upon the advice of another poster here, I’m diving into the YDKJS series. I’m toward the end of the one about Scopes and Closure, and I think I understand most of the concepts well enough, but I’m stuck on the illustration of closure using for-loops. The author writes:

for (var i=1; i<=5; i++) {
	setTimeout( function timer(){
		console.log( i );
	}, i*1000 );
}

The spirit of this code snippet is that we would normally expect for the behavior to be that the numbers “1”, “2”, … “5” would be printed out, one at a time, one per second, respectively.

In fact, if you run this code, you get “6” printed out 5 times, at the one-second intervals.

Huh?

Firstly, let’s explain where 6 comes from. The terminating condition of the loop is when i is not <=5. The first time that’s the case is when i is 6. So, the output is reflecting the final value of the i after the loop terminates.

This actually seems obvious on second glance. The timeout function callbacks are all running well after the completion of the loop. In fact, as timers go, even if it was setTimeout(…, 0) on each iteration, all those function callbacks would still run strictly after the completion of the loop, and thus print 6 each time.

The bold part is what I don’t understand. Why are the function callbacks (which I’m assuming is function timer()) running after the loop is terminated as opposed to concurrently as i increments?

I’m lost.


#2

Each loop iteration wraps the function timer in a setTimeout call which runs at least* i * 1000 milliseconds later. Computers are really, really fast at processing data, so it can run through a loop in almost no time at all, and it’s not until i seconds later (plus some extra milliseconds) that the function gets run.

*I say “at least” because setTimeout does not guarantee that the function will be called at that time, it just adds the function to the event queue to be executed when the browser gets to it.


#5

Unfortunately, I’m still feeling a little unclear. Perhaps if I could find a visual aid, it would help. I understand closure generally: it’s essentially the use of functions to access private variables outside of the scope in which they’re declared. So,

function petDog (pName, pBreed, pWeight) {
    var _name = pName;
    var _breed = pBreed;
    var _weight = pWeight;
    this.getInfo = function () {return _name + ", " + _breed + ", " + _weight;};
}

var myDog = new petDog ("Fido", "Black Lab", 100);

myDog.getInfo()

So, the property getInfo() has closure over the scope of the function myDog().

I think where my confusion is arising is how self-invoking functions relate to closure. The following example is from w3schools:

var add = (function () {
    var counter = 0;
    return function () {return counter += 1;}
})();

add();
add();
add();

// the counter is now 3

What it looks like is that the add() function is essentially skipping the section of code that initializes counter to 0. How is this happening? The explanation is:

The self-invoking function only runs once. It sets the counter to zero (0), and returns a function expression.

What constitute the boundaries of the self-invoking function? I thought the self-invoking function was everything between the parentheses that precede the final parentheses: (..self-invoking function..)();

If I can get a grasp on this, I might have a better chance of understanding the for-loops as well. Thanks in advance for your help!


#6

The value of add will be whatever the IIFE (immediately invoked function expression, or “iffy”) returns. In this case, it’s a function.

// Check the contents of add without invoking it
console.log(add); // function () {return counter += 1;}

That function has access to the scope of the function which created it, so it can access and update the variable counter. Counter is initialized only once, when the IIFE is run. After this, calling add() only invokes the function that gets returned. This is different from your first question, though they are both generally about closures and scope. So you are right about the boundaries of the IIFE, but keep in mind that only what gets returned from self-invoking functions will be stored in a variable, even though the function’s closure will maintain a reference to everything that was in the IIFE.


#7

here is your IIFE function on pythontutor … it will be easier to understand by stepping through the code https://goo.gl/6Ufsgr


#8

What is preventing the counter from being initialized to 0 each time the function in called?


#9

The function that’s being called is not the IIFE. counter is initialized only once, no matter how many times add is called. This is the function that runs when add is invoked:

function () {return counter += 1;}

Only that function runs. The IIFE only runs during variable assignment, and at that time counter is created in a closure.


#10

I had no idea this thing existed. Super fantastic!


#11

This is EXCELLENT! THANK YOU!


#12

I’m starting to feel like I’m getting to a place where the answer is “That’s just how it works.” I apologize if it’s getting frustrating.

I still don’t understand why counter is initialized when the function is declared (and executes itself), but the return function doesn’t run during that first execution. Then, each time add() is called, the return function runs, but the counter isn’t initialized.

It seems like sections of code are being skipped, either during the declaration and first run or during each subsequent call. I don’t understand how the computer knows what to skip or what syntax indicates that this is the way the code should run.


#13

The function being returned is just a value. You can return functions just like strings, numbers, booleans, or any other data type. Functions are only run when they’re invoked. It may help to rewrite your example slightly:

var add = (function () {
    var incrementCounter = function () {return counter += 1;};  // This assigns the function to a variable
    var counter = 0;
    return incrementCounter;  // Here, we return the function, but we're not running it
})();

Think about the difference between return incrementCounter and return incrementCounter(). Take a look at the example @JohnL3 posted. Sleep on it and see if your confusion clears up.


#14

Adding onto what @PortableStick was saying, the following is another way of saying the same thing. I used his naming conventions, so you can see what is happening. The difference is instead of executing an anonymous function (the var add = (function () {…), I gave the function the name initializeCounter, so that I could have a function name to call when assigning to add. They are doing the same thing.

function initializeCounter() {
  var incrementCounter = function () {return counter += 1;}
  var counter = 0;
  return incrementCounter;
}
var add = initializeCounter();

add();
add();
add(); // results in 3

The above declaration of var add = initializeCounter() executes the initializeCounter function which will turn add into the initializeCounter inside initializeCounter while remembering all variables and their variables values. Hopefully, this will help by giving a slightly different approach. I struggled with closure in the beginning also. It just takes time studying others’ examples, before it will finally click.

EDIT: Just to clarify something. My version would be even more like @PortableStick, if he would have named his anonymous function . See below where I have created a name for his anonymous function to match the same name as mine above.

var add = (function initializeCounter() {
    var incrementCounter = function () {return counter += 1;};  // This assigns the function to a variable
    var counter = 0;
    return incrementCounter;  // Here, we return the function, but we're not running it
})();

#15

I was just playing around with some of the new ES6 syntax and created the same example. That is short!

var add = ((counter=0) => () => ++counter)();

Something else I just noticed is you can also remove the 1st set of parentheses which wraps the outer function in the example @ekazubuike gave near the top of this post and it still works.

var add = function () {
  var counter = 0;
  return function () {return counter += 1;}
}();

For some reason, I had thought those wrapping parentheses had a purpose other than just a visual cue.


#16

I know this has already been answered several times but I’m hoping this explanation will help somebody.

Personally I am also not a fan of “that’s just how it works” explanations. I always want to know the origin and the reasoning for why something works the way it does.

Let’s look at this code in detail

for (var i=1; i<=5; i++) {
  setTimeout( function timer(){
    console.log( i );
  }, i * 1000 );
}

The reason why this behaves the way it does lies in the nature of the setTimeout function.
The function is known as an asynchronous function. This means that whenever the interpreter sees a call to such a function it doesn’t run it immediately but puts it on a “back burner” for later. The “later” happens when all the regular (synchronous) code has been executed.

After all regular code has been executed, the interpreter goes back and runs all the asynchronous functions that have been put on the queue.

In this case the for loop is synchronous code.

This is what an interpreter actually sees

for (var i=1; i<=5; i++) {
   //put something on the todo list for later
}

The interpreter runs this loop in a fraction of a second and puts 5 things on the “to-do” one after another. It only takes a very small amount of time to register the function on the queue. At this point no inner code has run.

When the interpreter finally gets around to running the 5 actions that have been put on the “later” queue, the for loop is long since finished running, and the variable i has been set to 6. This is because the last action that for loop executed is i++ making i equal 6, right before the condition check i <= 5 failed (because i is no longer less then or equal to 5)

In short by looking at this for loop we can see that the necessary condition for it to terminate would be met when i becomes 6

Finally the interpreter gets to running those 5 actions that were put on the queue earlier which means it will attempt to run console.log(i) 5 times.

The problem is that it uses i which is long since been set to 6, the actions that were waiting in queue had no way of retaining the value of i that was passed in when they were registered. All they know is to look for i whatever it is now. And now it is equal to 6.

The easiest way to solve this problem is to wrap everything inside the loop in a IIFE. This is an Immediately Invoked Function Expression. In a nutshell it’s simply an anonymous function which is immediately called
(function(){

})();

On the surface it might look like this has no purpose, but the reason this is important is because in javascript a function retains its context. When you call setTimeout() and set the timer() function to execute, the i variable does not exist inside timer() so it uses the i from the outer context. In this case the i that ends up being used is the i that has long since been set to 6.

Now let’s write the same code with an IIFE

for (var i=1; i<=5; i++) {
  (function(i){
    setTimeout( function timer(){
      console.log( i );
    }, i * 1000 );
  })(i);
}

This code will run exactly as expected.

What is happening here is that as the for loop runs 5 times (very fast) it calls the anonymous function 5 times (each time with a different i). Then the anonymous function proceeds to set the timeout. Effectively this creates a new i for each call, because javascript uses function scoping. i inside the IIFE is not the same i that the for loop is incrementing. This time when the interpreter finally gets around to running the timer function, the i that i will use is the i that was passed to the IIFE, and not the global i that was modified by the for loop.

I hope i have made that more clear for you.

If you have any questions let me know.


#17

Great explanation! After reading the last paragraph, I modified your code to show the two different i’s (global_i and inner_i) mentioned in your last paragraph.

for (var global_i=1; global_i<=5; global_i++) {
  (function(inner_i){
    setTimeout( function timer(){
      console.log( inner_i );
    }, inner_i * 1000 );
  })(global_i);
}