0

var funcs=[];
for(let i=0;i<3;i++){
 funcs[i]=function(){
  
  return i;
 }
 
}
alert(funcs[1]);
alert(funcs[1]());
The window alert 2 times. The first one like this:

function (){
  return i;
 }

The second one like this

1

But I don't konw why the funcs[1] can executed not reporting an error 'i is undefined';

zyMacro
  • 675
  • 7
  • 14

1 Answers1

4

Because the function created within the loop closes over the lexical environment that's active when it was created. That lexical environment is (conceptually) an object, which contains locals defined within it (and some other things), including the i variable, in this case the one created for that specific iteration of the loop body (because of the very special way for treats let declarations in its initializer). This is the concept of a "closure," one of the central technologies of JavaScript. Even when execution passes out of the scope a given lexical environment is associated with (a function returns, we move on to the next iteration of the loop, etc.), if anything still has a reference to that environment object, like all objects it lives on.

Because of how for handles let in its initializer, each entry in funcs gets its own lexical environment, and thus its own copy of i.

When you call one of those functions, a new environment object is created with its "outer" environment set to the environment attached to the function. When you reference i within the function code, it looks first at the function's environment and, if i isn't found there, it looks to the outer environment — where it finds it (in this case) and uses it.

In a comment you've said

If you use 'var' not 'let', it will always return '3'

Exactly right. With var, i would be hoisted up to the environment object associated with the function the for loop is in (or the global one, if this is global code). So all of the functions createed in the loop share the same i, which by the time you're calling them has the value 3.

This is one of the essential differences between let/const and var: let and const have block scope, and for has special handling for let in its initializer.

Let's follow through these various environment objects as they get created. Assuming this code:

funtion example() {
    const funcs = [];
    for (let i = 0; i < 3; ++i) {
        funcs[i] = function() {
            return i;
        };
    }
    funcs[1](); // 1
}
example();

When we call example, after the const funcs = [] but before the for loop starts, the current environment object is the one created for the call to example, so we have something like this in memory (ignoring some details):

                                          +−−−−−−−−−−−−−−−−+
    current env>−−−−−−−−−−−−−−−−−−−−−−−−−>| `example` Call |
                                          |  Env Object    |
                                          +−−−−−−−−−−−−−−−−+
                                          | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
                                          | funcs          |>−+
                                          +−−−−−−−−−−−−−−−−+  |  +−−−−−−−−−−−+
                                                              +−>|  (array)  |
                                                                 +−−−−−−−−−−−+
                                                                 | length: 0 |
                                                                 +−−−−−−−−−−−+

Now, the for loop starts: A new per-iteration environment object is created put in place as the "current" one, with the previous one as its "outer" environment. An i variable is created within that new per-iteration environment object, and given the value 0:

                  +−−−−−−−−−−−−−−+
   current env>−−>| Iteration 0  |
                  | Env Object   |
                  +−−−−−−−−−−−−−−+         +−−−−−−−−−−−−−−−−+
                  | [[Outer]]    |>−−−−−−−>| `example` Call |                                                  
                  | i: 0         |         |  Env Object    |                                                  
                  +−−−−−−−−−−−−−−+         +−−−−−−−−−−−−−−−−+                                                  
                                           | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
                                           | funcs          |>−+
                                           +−−−−−−−−−−−−−−−−+  |  +−−−−−−−−−−−+
                                                               +−>|  (array)  |
                                                                  +−−−−−−−−−−−+
                                                                  | length: 0 |
                                                                  +−−−−−−−−−−−+

During the loop iteration, we create a function and store it in funcs. The function gets a reference to the current environment object, which it keeps as [[Environment]] (this is an implementation thing, you won't actually find that if you look at the function, it's not accessible at the code level, just within the JavaScript engine):

                   +−−−−−−−−−−−−−−+
    current env>−+>| Iteration 0  |
                /  | Env Object   |         +−−−−−−−−−−−−−−−−+
               /   +−−−−−−−−−−−−−−+         | `example` Call |
              +    | [[Outer]]    |>−−−−−−−>|  Env Object    |
              |    | i: 0         |         +−−−−−−−−−−−−−−−−+
              |    +−−−−−−−−−−−−−−+         | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
              |                             | funcs          |>−+
              |                             +−−−−−−−−−−−−−−−−+  |  +−−−−−−−−−−−+
              |                                                 +−>|  (array)  |
              |                                                    +−−−−−−−−−−−+
              |                                                    | length: 1 |       +−−−−−−−−−−−−−−−−−+
              |                                                    | 0         |>−−−−−>|   Function 0    |
              |                                                    +−−−−−−−−−−−+       +−−−−−−−−−−−−−−−−−+
              |                                                                        | [[Environment]] |>−−−−−+
              |                                                                        +−−−−−−−−−−−−−−−−−+      |
              |                                                                                                 |
              +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

Now this is where the clever handling of let in for initializers works (and in fact, the clever handling of let and const within a for loop's body as well): A new environment object for the next iteration is created, and the value of i is copied from the i for the previous iteration to the i for the next iteration. So we have:

                   +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−>| Iteration 0  |
|                  | Env Object   |         +−−−−−−−−−−−−−−−−+
|                  +−−−−−−−−−−−−−−+         | `example` Call |
|                  | [[Outer]]    |>−−−+−−−>|  Env Object    |
|                  | i: 0         |   /     +−−−−−−−−−−−−−−−−+
|                  +−−−−−−−−−−−−−−+  +      | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
|                                    |      | funcs          |>−−−+
|                                    |      +−−−−−−−−−−−−−−−−+    |   +−−−−−−−−−−−+
|                                    |                            +−−>|  (array)  |
|                  +−−−−−−−−−−−−−−+  |                                +−−−−−−−−−−−+
|   current env>−+>| Iteration 1  |  |                                | length: 1 |     +−−−−−−−−−−−−−−−−−+
|                  | Env Object   |  |                                | 0         |>−−−>|   Function 0    |
|                  +−−−−−−−−−−−−−−+  |                                +−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−+
|                  | [[Outer]]    |>−+                                                  | [[Environment]] |>−+
|                  | i: 0         |                                                     +−−−−−−−−−−−−−−−−−+  |
|                  +−−−−−−−−−−−−−−+                                                                          |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

Then the i in that new environment is incremented to 1, and a new function is created and stored in funcs, giving us:

                   +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−>| Iteration 0  |
|                  | Env Object   |         +−−−−−−−−−−−−−−−−+
|                  +−−−−−−−−−−−−−−+         | `example` Call |
|                  | [[Outer]]    |>−−−+−−−>|  Env Object    |
|                  | i: 0         |   /     +−−−−−−−−−−−−−−−−+
|                  +−−−−−−−−−−−−−−+  +      | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
|                                    |      | funcs          |>−−−+
|                                    |      +−−−−−−−−−−−−−−−−+    |   +−−−−−−−−−−−+
|                                    |                            +−−>|  (array)  |
|                  +−−−−−−−−−−−−−−+  |                                +−−−−−−−−−−−+
|   current env>−+>| Iteration 1  |  |                                | length: 2 |     +−−−−−−−−−−−−−−−−−+
|               /  | Env Object   |  |                                | 0         |>−−−>|   Function 0    |
|              /   +−−−−−−−−−−−−−−+  |                                | 1         |>−+  +−−−−−−−−−−−−−−−−−+
|             +    | [[Outer]]    |>−+                                +−−−−−−−−−−−+  |  | [[Environment]] |>−−−+
|             |    | i: 1         |                                                  |  +−−−−−−−−−−−−−−−−−+    |
|             |    +−−−−−−−−−−−−−−+                                                  |                         |
|             |                                                                      |  +−−−−−−−−−−−−−−−−−+    |
|             |                                                                      +−>|   Function 1    |    |
|             |                                                                         +−−−−−−−−−−−−−−−−−+    |
|             |                                                                         | [[Environment]] |>−+ |
|             |                                                                         +−−−−−−−−−−−−−−−−−+  | |
|             |                                                                                              | |
|             +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ |
|                                                                                                              |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

Then at the end of that iteration, we do the whole thing again for the last iteration, and get:

                   +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−>| Iteration 0  |
|                  | Env Object   |         +−−−−−−−−−−−−−−−−+
|                  +−−−−−−−−−−−−−−+         | `example` Call |
|                  | [[Outer]]    |>−−+−−+ −>|  Env Object    |
|                  | i: 0         |  /  /   +−−−−−−−−−−−−−−−−+
|                  +−−−−−−−−−−−−−−+  | |    | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
|                                    | |    | funcs          |>−−−+
|                                    | |    +−−−−−−−−−−−−−−−−+    |   +−−−−−−−−−−−+
|                                    | |                          +−−>|  (array)  |
|                  +−−−−−−−−−−−−−−+  | |                              +−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−>| Iteration 1  |  | |                              | length: 3 |       +−−−−−−−−−−−−−−−−−+
| |                | Env Object   |  | |                              | 0         |>−−−−−>|   Function 0    |
| |                +−−−−−−−−−−−−−−+  | |                              | 1         |>−−−+  +−−−−−−−−−−−−−−−−−+
| |                | [[Outer]]    |>−+ |                              | 2         |>−+ |  | [[Environment]] |>−−−−−+
| |                | i: 1         |    |                              +−−−−−−−−−−−+  | |  +−−−−−−−−−−−−−−−−−+      |
| |                +−−−−−−−−−−−−−−+    |                                             | |                           |
| |                                    |                                             | |  +−−−−−−−−−−−−−−−−−+      |
| |                +−−−−−−−−−−−−−−+    |                                             | +−>|   Function 1    |      |
| | current env>−+>| Iteration 2  |    |                                             |    +−−−−−−−−−−−−−−−−−+      |
| |             /  | Env Object   |    |                                             |    | [[Environment]] |>−−−+ |
| |            +   +−−−−−−−−−−−−−−+    |                                             |    +−−−−−−−−−−−−−−−−−+    | |
| |            |   | [[Outer]]    |>−−−+                                             |                           | |
| |            |   | i: 2         |                                                  |    +−−−−−−−−−−−−−−−−−+    | |
| |            |   +−−−−−−−−−−−−−−+                                                  +−−−>|   Function 2    |    | |
| |            |                                                                          +−−−−−−−−−−−−−−−−−+    | |
| |            |                                                                          | [[Environment]] |>−+ | |
| |            |                                                                          +−−−−−−−−−−−−−−−−−+  | | |
| |            |                                                                                               | | |
| |            +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | |
| |                                                                                                              | |
| +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ |
|                                                                                                                  |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

When we call funcs[1](), an environment for the call is created and its [[Outer]] environment is set to the [[Environment]] of the function. So just before we return i in that function, we have (leaving out some details):

               +−−−−−−−−−−−−−−+
current env>−−>| Call to      |
               | `funcs[1]()` |
               | Env Object   |
               +−−−−−−−−−−−−−−+
               | [[Outer]]    |>−−+
               +−−−−−−−−−−−−−−+   |
                                  |    
                                  |       +−−−−−−−−−−−−−−−−+
             +−−−−−−−−−−−−−−−−−−−−+       | `example` call |
             |                       +−−−>| Env Object     |
             |                       |    +−−−−−−−−−−−−−−−−+
             |                       |    | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
             |                       |    | funcs          |>−+
             |                       |    +−−−−−−−−−−−−−−−−+  |  +−−−−−−−−−−−+
             |                       |                        +−>|  (array)  |
             \     +−−−−−−−−−−−−−−+  |                           +−−−−−−−−−−−+
  +−−−−−−−−−−−+−−−>| Iteraton 1   |  |                           | length: 3 |       +−−−−−−−−−−−−−−−−−+
  |                | Env Object   |  |                           | 0         |>−−−−−>|   (function)    |
  |                +−−−−−−−−−−−−−−+  |                           | 1         |>−−−+  +−−−−−−−−−−−−−−−−−+
  |                | [[Outer]]    |>−+                           | 2         |>−+ |  | [[Environment]] |>...
  |                | i: 1         |                              +−−−−−−−−−−−+  | |  +−−−−−−−−−−−−−−−−−+
  |                +−−−−−−−−−−−−−−+                                             | |                     
  |                                                                             | |  +−−−−−−−−−−−−−−−−−+
  |                                                                             | +−>|   (function)    |
  |                                                                             |    +−−−−−−−−−−−−−−−−−+
  |                                                                             |    | [[Environment]] |>−−−−+
  |                                                                             |    +−−−−−−−−−−−−−−−−−+     |
  |                                                                             |                            |
  |                                                                             |    +−−−−−−−−−−−−−−−−−+     |
  |                                                                             +−−−>|   (function)    |     |
  |                                                                                  +−−−−−−−−−−−−−−−−−+     |
  |                                                                                  | [[Environment]] |>... |
  |                                                                                  +−−−−−−−−−−−−−−−−−+     |
  |                                                                                                          |
  |                                                                                                          |
  +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

When the function looks up i, it looks in the current environment object. Since it's not there, it looks to the [[Outer]] object. It finds it there, with the value 1, so that's the value it uses.

In contrast, if we use var the i is hoisted to the environment object for the call to example (where funcs is), so after the loop we have this instead (note that i is no longer on the per-iteration environments):

                   +−−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−>| Iteration 0  |
|                  | Env Object   |         +−−−−−−−−−−−−−−−−+
|                  +−−−−−−−−−−−−−−+         | `example` Call |
|                  | [[Outer]]    |>−−+−−+−>|  Env Object    |
|                  +−−−−−−−−−−−−−−+  /  /   +−−−−−−−−−−−−−−−−+
|                                    | |    | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
|                                    | |    | i: 3           |
|                                    | |    | funcs          |>−−−+
|                                    | |    +−−−−−−−−−−−−−−−−+    |   +−−−−−−−−−−−+
|                                    | |                          +−−>|  (array)  |
|                  +−−−−−−−−−−−−−−+  | |                              +−−−−−−−−−−−+
| +−−−−−−−−−−−−−−−>| Iteration 1  |  | |                              | length: 3 |       +−−−−−−−−−−−−−−−−−+
| |                | Env Object   |  | |                              | 0         |>−−−−−>|   Function 0    |
| |                +−−−−−−−−−−−−−−+  | |                              | 1         |>−−−+  +−−−−−−−−−−−−−−−−−+
| |                | [[Outer]]    |>−+ |                              | 2         |>−+ |  | [[Environment]] |>−−−−−+
| |                +−−−−−−−−−−−−−−+    |                              +−−−−−−−−−−−+  | |  +−−−−−−−−−−−−−−−−−+      |
| |                                    |                                             | |                           |
| |                                    |                                             | |  +−−−−−−−−−−−−−−−−−+      |
| |                +−−−−−−−−−−−−−−+    |                                             | +−>|   Function 1    |      |
| | current env>−+>| Iteration 2  |    |                                             |    +−−−−−−−−−−−−−−−−−+      |
| |             /  | Env Object   |    |                                             |    | [[Environment]] |>−−−+ |
| |            +   +−−−−−−−−−−−−−−+    |                                             |    +−−−−−−−−−−−−−−−−−+    | |
| |            |   | [[Outer]]    |>−−−+                                             |                           | |
| |            |   +−−−−−−−−−−−−−−+                                                  |    +−−−−−−−−−−−−−−−−−+    | |
| |            |                                                                     +−−−>|   Function 2    |    | |
| |            |                                                                          +−−−−−−−−−−−−−−−−−+    | |
| |            |                                                                          | [[Environment]] |>−+ | |
| |            |                                                                          +−−−−−−−−−−−−−−−−−+  | | |
| |            |                                                                                               | | |
| |            +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ | |
| |                                                                                                              | |
| +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ |
|                                                                                                                  |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

Which means when we call funcs[1](), a new environment is created for it, its [[Outer]] is set to the function's [[Environment]], and just before return i we have:

               +−−−−−−−−−−−−−−+
current env>−−>| Call to      |
               | `funcs[1]()` |
               | Env Object   |
               +−−−−−−−−−−−−−−+
               | [[Outer]]    |>−−+
               +−−−−−−−−−−−−−−+   |
                                  |    
                                  |    
             +−−−−−−−−−−−−−−−−−−−−+       +−−−−−−−−−−−−−−−−+
             |                            | `example` call |
             |                       +−−−>| Env Object     |
             |                       |    +−−−−−−−−−−−−−−−−+
             |                       |    | [[Outer]]      |>−−−>(The env obj for when `example` was created.)
             |                       |    | i: 3           |
             |                       |    | funcs          |>−+
             |                       |    +−−−−−−−−−−−−−−−−+  |  +−−−−−−−−−−−+
             |                       |                        +−>|  (array)  |
             \     +−−−−−−−−−−−−−−+  |                           +−−−−−−−−−−−+
  +−−−−−−−−−−−+−−−>| Iteration 1  |  |                           | length: 3 |       +−−−−−−−−−−−−−−−−−+
  |                | Env Object   |  |                           | 0         |>−−−−−>|   (function)    |
  |                +−−−−−−−−−−−−−−+>−+                           | 1         |>−−−+  +−−−−−−−−−−−−−−−−−+
  |                | [[Outer]]    |                              | 2         |>−+ |  | [[Environment]] |>...
  |                +−−−−−−−−−−−−−−+                              +−−−−−−−−−−−+  | |  +−−−−−−−−−−−−−−−−−+
  |                                                                             | |                     
  |                                                                             | |  +−−−−−−−−−−−−−−−−−+
  |                                                                             | +−>|   (function)    |
  |                                                                             |    +−−−−−−−−−−−−−−−−−+
  |                                                                             |    | [[Environment]] |>−−−−+
  |                                                                             |    +−−−−−−−−−−−−−−−−−+     |
  |                                                                             |                            |
  |                                                                             |    +−−−−−−−−−−−−−−−−−+     |
  |                                                                             +−−−>|   (function)    |     |
  |                                                                                  +−−−−−−−−−−−−−−−−−+     |
  |                                                                                  | [[Environment]] |>... |
  |                                                                                  +−−−−−−−−−−−−−−−−−+     |
  |                                                                                                          |
  |                                                                                                          |
  +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

So when the function looks up i, it doesn't find it in the current environment, and it doesn't find it in the first [[Outer]] environment, but it does find it in the second [[Outer]] environment, with the value 3, so that's the value it uses.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thank you. But I still have some questions.What did it mean 'the i isn't connected to the execution context created for the loop iteration, it's connected to the execution context containing your for loop '? And when did it happend 'So each entry in funcs gets its own copy of i'? – zyMacro Jan 05 '17 at 13:15
  • Thank you so much. But It's late at night in China. I will read it clearly in tomorrow morning.Can't wait to it. – zyMacro Jan 05 '17 at 15:33
  • Picture is very clearly, I think I already understand. I’m truly grateful for your help. – zyMacro Jan 06 '17 at 02:05
  • Furthermore, if you use function.bind(this,i) in a loop, what does the "this" stands for?The each Iterration Env Object in your pic? – zyMacro Jan 06 '17 at 02:11
  • 1
    @zyMacro: No, `this` is the same throughout a function call, and `this` is never a reference to an environment object. (There's no mechanism in JavaScript to get a direct reference to an environment object, and in fact, while it's there conceptually, an implementation is free to optimize it out entirely so long as the code behaves properly.) – T.J. Crowder Jan 06 '17 at 07:26
  • So, what is the **'this'** corresponding to when you use `funcs[i]=function(){ return i; }.bind(this,i); ` in the loop? – zyMacro Jan 06 '17 at 08:14
  • @zyMacro: Whatever it is `this` is in the code containing the `for` loop; it doesn't matter and isn't affected by the `for` loop. But there's no need for that `bind` when you're using `for` with `let`. – T.J. Crowder Jan 06 '17 at 10:33
  • Hi @T.J. Crowder, I have a question. Whenever there is a code block (code between braces) and at least ONE variable declared with "let" within that code block is that code block creating a new LEXICAL ENVIRONMENT? Thanks in advance –  Oct 18 '22 at 14:24
  • 1
    @Daniel - As far as the spec is concerned, a new Declarative Environment Record is set up on the executions context's LexicalEnvironment regardless of whether there are any lexical declarations within the block: https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-block-runtime-semantics-evaluation I suspect JavaScript engines optimize that away when there's no point to it though. :-) – T.J. Crowder Oct 18 '22 at 15:13
  • 1
    Great now @T.J. Crowder I understand, the "effect" of having a separate environment "is not visible" if you don't have block scope declarations, therefore, it might be entirely possible for engines to avoid creating an additional environment in that case. +1 and marked as a useful answer, thank you very much. –  Oct 19 '22 at 11:01