About the key word 'let' in JavaScript

93 views Asked by At

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';

1

There are 1 answers

10
T.J. Crowder On BEST ANSWER

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.