Closure and For-Loops

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.

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.

1 Like

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!

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.

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

1 Like

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

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.

I had no idea this thing existed. Super fantastic!

This is EXCELLENT! THANK YOU!

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.

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.

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.

7 Likes

I see it like this: what’s being return is a reference to the function. A reference looks at the memory location of the variable for the value not the initialized value of the variable. Actually ot’s a memory location of the function. If you change the value at memory location the variables initial value is over written. I hope that helps. It’s a little confusing to me too.

To refer to your add example from W3Schools

The add function isn’t executed as an IIFE. The anonymous function that is being assigned to add is executed as the IIFE. This anon function only gets executed once because it is an IIFE. As soon as the code executes the anon function runs, which happens only once. The anonymous function itself is returning an internal function to the variable add. When the add variable is executed as a function, then the inner return function is being executed and increments the counter.

A better way to demonstrate this is not to use an IIFE with the add function, but instead make add a function expression. And then also give the internal return function a name within add.

var add = (function () {
  var counter = 0;
  console.log("Add function's counter variable: " + counter); 
  return function counterFunc() {counter += 1; return counter}
});

//This is executing the add func, setting counter variable to 0 
//This console will output the returned counterFunc function. 
//Add function will log the counter variable = 0
console.log(add());  

//We're executing the add function and assigning the return function to 'increment' variable declaration
//This is also setting counter variable to 0 & add function is outputting the counter variable = 0 
var increment = add(); 

//We're calling the internal function within the add function and executing it, aka the counterFunc()
console.log(increment());  //1
console.log(increment());  //2
console.log(increment());  //3
console.log(increment());  //4

//If we call add function again. The counter will be reset to 0 & add function will log counter as = 0 
increment = add(); 
//now call internal function counterFunc and you see counter has been reset 
console.log(increment());  //1
console.log(increment());  //2

Side Note: You can also declare your add function expression as the below. Parenthesis around the anon function are required for the IIFE

var add = function () {
  var counter = 0;
  console.log("Add function's counter variable: " + counter); 
  return function counterFunc() {counter += 1; return counter}
};