简体   繁体   中英

Trying to understand scope and closures

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)
}

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) }

Your example 2 , ie :

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM