0

I've been going through a few tutorials to understand scoping and closures in JavaScript and came across the code below.

I understand the first block where the output is 5,5,5,5,5 because the function executes after the for loop has finished. However I don't fully understand why the second block works...am I right in thinking that on each iteration a new function is invoked, so there are 5 functions running at the same time in memory? I'd like a simple to understand explanation please - I'm new to learning JavaScript.

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log('index: ' + i);
  }, 1000);
}


for (var i = 0; i < 5; i++) {
  (function logIndex(index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}
Ates Goral
  • 137,716
  • 26
  • 137
  • 190
j obe
  • 1,759
  • 4
  • 15
  • 23
  • Javascript is call by value for primitives. Replace the second example with `for (var i = { i: 0 }; i.i < 5; i.i++) {` and `console.log('index: ' + index.i);` and you will get the same issue as the first example again. – ASDFGerte Jun 02 '18 at 13:01
  • I've also just come across this code in another tutorial and am puzzled as it looks very similar to the first example here except it prints out 10 at 1 second intervals and I don't understand why. It's related to this so I'm adding it as a comment instead of a separate thread: for (var i = 0; i < 10; i++) {setTimeout(function() { console.log(i); }, i*1000); } – j obe Jun 02 '18 at 13:02
  • Possible duplicate of [JavaScript closure inside loops – simple practical example](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – ASDFGerte Jun 02 '18 at 13:12
  • Note that while the above linked question is not exactly the same as this, there are multiple indications that it would solve a majority of the problem - e.g. "why the let would work and the var doesn't", scoping rules, further examples and tons of material on the issue in general. Both code snippets also frequently occur in the answers and are explained. – ASDFGerte Jun 02 '18 at 13:15
  • @ASDFGerte The majority of the problem isn't why the let would work and the var doesn't, that was a reply to a comment that was made. I'm sure you could also say there's plenty of material on any question that's asked on here. – j obe Jun 02 '18 at 13:21
  • Reasons why i frequently flag questions as duplicate. The linked topic is also not primarily about why let works and var doesn't but about your issue. The top rated answer already states 'Since there is no block scope in JavaScript - only function scope - by wrapping the function creation in a new function, you ensure that the value of "i" remains as you intended.' - just that it describes it as a solution instead of a problem. But there are many more answers and comments about the specific issue. – ASDFGerte Jun 02 '18 at 13:25
  • @ASDFGerte I put in bold that I'm looking for a simple to understand explanation and in a comment previously I've mentioned a walkthrough would be helpful for me. Your comments haven't helped me understand any further. – j obe Jun 02 '18 at 13:28

4 Answers4

0

Yes you are right 5 function will execute and there is no need of logIndex you can use anonymous function for this type of work.

(funtion(index){}) => function defination

(funtion(index){})(i) => calling function with passing i.

for (var i = 0; i < 5; i++) {
  (function (index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}
Nishant Dixit
  • 5,388
  • 5
  • 17
  • 29
0

Your example 2 , i.e :

for (var i = 0; i < 5; i++) {
  (function logIndex(index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}

Works Fine because in the example you arrange for a distinct copy of "i" to be present for each of the timeout functions using closures.

You can even use let to achieve it, try the following:

for (let i = 0; i < 5; i++) {
    setTimeout(function () {
      console.log('index: ' + i);
    }, 1000);
}

This works because in a loop with a let-based index, each iteration through the loop will have a new value of i where each value is scoped inside the loop, so your code would work as you expect.

For further explanation you can refer : Reference

amrender singh
  • 7,949
  • 3
  • 22
  • 28
  • Thanks - I don't understand why the let would work and the var doesn't - aren't they both doing the same thing here, iterating up to 5? – j obe Jun 02 '18 at 13:08
  • let works fine because let variable is associated with block scope, whereas a var variable has functional scope. Therefore in your case, the var i bound to closest functional scope, which is window. Where as let i will have a new value each time which will be scoped inside loop. – amrender singh Jun 02 '18 at 13:12
  • I understand now, maybe a walk-through example would have helped initially. – j obe Jun 02 '18 at 13:24
  • @jobe Please refer to the reference link mentioned in answer, it has many examples along with step by step explanation. I hope it helps. – amrender singh Jun 02 '18 at 13:26
0

Refactoring my answer due to your comments below.

Before we begin we need to address couple of terms:

  1. Execution context - In simple terms, this is the "environment" that a function executes in. As an example, when our application starts we run on the "global" execution context, when we invoke a function we create a new execution context (nested inside the global).
    Each execution-context has a variable-environment (the scope) and of course the body of the function (it's "commands").

  2. Call Stack - To keep track on which execution-context we are on, and which variables are available for us each execution-context is pushed to the call-stack, when the function returns it popped-out from the call-stack and it's environment is flagged to be garbage collected (free up memory), except for one exception which we will find out later on.

  3. Web-browser API and the event loop - JavaScript is single threaded (let's call it a thread for simplicty), but some times we need to handle asynchronous actions such as click-events, xhr and timers.
    The browser exposes them via it's API, addEventListener, XHR / fetch, setTimeout etc...
    The cool thing here is that it will run it on a different thread then the javascript's thread. But how can the browser run our code on the main thread? via callbacks that we provide to it (like you did with setTimeout).
    Ok, when will it run our code? we want a predictable way to run our code.
    Enters the event-loop and the callback-Que, the browser pushes each callback to this que (promises goes to a different que with higher priority by the way) and the event loop is watching the call-stack, when ever the call-stack is empty and there is no more code to run in global the event loop will grab the next callback and push it to the call-stack.

  4. Closure - In simple words, it's when a function accessing it's lexical (static) scope even if it runs outside of it. it will be clearer later on.


In example #1 - We run a loop on the global execution context, creating a variable i and changing it to a new value on each iteration, while passing 5 callbacks to the browser (via the setTimeout API).
The event-loop can't push those callbacks back to the call-stack because it is not yet empty. when the loop has finished though, the call-stack is empty and the event-loop pushes our callbacks to it, each callback accessing the i and printing the latest value 5 (closure, we access i enviroment way after it was supposed to be destroyed). the reason for this is that all callbacks were created on the same execution context, thus they reference the same i.

In example #2 - We run a loop on the global execution context, creating a new function (IIFE) on each iteration, thus creating a new execution-context. this will create a copy of i inside this execution-context and not in global context like before. Inside this execution-context we send off a callback via setTimeout, just like before the event loop waits until the loop has finished so the call-stack will be empty and pushes the next callback to the stack. but now when the callback runs, it access it's execution-context where it was created and prints the i that never changed by the global context.

So basically we have 5 execution contexts (without global) that each has its own i.

Hope that is clearer now.

I really recommend watching this video about the event loop.

Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
  • I asked for a simple to understand explanation and you're mentioning event loops, call stacks and things which I haven't covered yet. – j obe Jun 02 '18 at 13:30
  • Aw sory, you mentioned that you understand the first block so i assumed you are familiar with these terms. I mean if you realy understand why the first code runs after the for loop has finnish then you should know about the event loop. I don't mind to try explaining it if you want – Sagiv b.g Jun 02 '18 at 13:35
  • No problem, I understand some parts of what is happening but just not the difference. Nobody has answered if 5 functions are running at the same time in memory so far either, a simple explanation would help me. If you think you can explain the event loop in a simple way, then I'm willing to understand. – j obe Jun 02 '18 at 13:39
  • `setTimeout however is not running on javascript's thread` just a small niggle, no threads are involved here. Not even a worker thread, timers are interupt based. – Keith Jun 02 '18 at 13:53
0
for (var i = 0; i < 5; i++) {
    (function logIndex(index) {
        setTimeout(function () { console.log(index); }, 1000); // 0 1 2 3 4
    })(i)
}

Your code internally will create a closure bound with the current index value for the each iteration. So totally you were creating 5 closure's with different index values.

Once the setTimeout elapses inside the each closure's, it prints their locally visible index value.

SridharKritha
  • 8,481
  • 2
  • 52
  • 43