Search code examples
javascriptclosuresv8

how values are passed to callbacks in javascript


I want to understand how and when javascript engines pass values to callback functions, I have tried debugging and looking up online but I am unable to find exact answer, consider the following example:

for(var i = 0; i < 4; i++) {
    console.log("outside callback " + i + " ");

    setTimeout(function test(){
      console.log("inside callback " + i + " ");
    },100);
}

this prints following output

outside callback 0

outside callback 1

outside callback 2

outside callback 3

inside callback 4

inside callback 4

inside callback 4

inside callback 4

If I just change the declaration of i variable using let keyword as follows:

for(let i = 0; i < 4; i++) {
    console.log("outside callback " + i + " ");
    
    setTimeout(function test(){
       console.log("inside callback " + i + " ");
    },100);
}

It results in the following output:

outside callback 0

outside callback 1

outside callback 2

outside callback 3

inside callback 0

inside callback 1

inside callback 2

inside callback 3

When debugging this in Chrome, it shows i in first example as closure scope for test function and block scope in second example.


Solution

  • This relates to a frequently asked question about JavaScript closure inside loops – simple practical example , which goes into detail about why inside call back functions created in a loop, variables declared using var have the values they reached when the loop ended: while they may have held different values when the callback was setup, when the call back is performed, asynchronously, some time later, the loop has long finished and left variables inside a closure with the values they held when the loop completed.

    From ECMAScript version 6 onwards, variables declared using let are block scoped and cannot be accessed outside the code block in which they are defined. In addition variables defined in a for loop control statement receive special treatment.

    1. Their scope is that of the for loop body, not the block of code containing the for loop.
    2. A new binding for let variables is created for each iteration of the loop. This means each iteration can have its own set of variable values, held in a lexical environment record created for the iteration, that can be used in a closure and accessed by callback functions defined within the loop.

    3. The value of loop counters in each lexical environment record is designed to make the use of multiple environment records largely transparent:

      for( part1; part2; part3) {
          // loop body code
      }
      
      • In the first iteration, let defined variables within the for statement have the values they would have after executing part1 and part2.
      • In subsequent iterations, they have the values determined by initializing their values in the environment record for the current iteration with those they had at the end of the previous iteration, and then executing part3 and part2 in the for statement above.
      • call back functions declared inside the for loop body access the value of let defined variables held at the end of the iteration in which the call back was set up, excluding side effects of evaluating part3 of the for statement.