up
|
@ -1,23 +1,23 @@
|
|||
|
||||
# Closure
|
||||
|
||||
JavaScript is a very function-oriented language, it gives a lot of freedom. A function can be created at one moment, then passed as a value to another variable or function and called from a totally different place much later.
|
||||
JavaScript is a very function-oriented language. It gives a lot of freedom. A function can be created at one moment, then copied to another variable or passed as an argument to another function and called from a totally different place later.
|
||||
|
||||
We know that a function can access variables outside of it. And this feature is used quite often.
|
||||
|
||||
But what happens when outer variables change? Does a function get a new value or the old one?
|
||||
But what happens when an outer variables changes? Does a function get a most recent value or the one that existed when the function was created?
|
||||
|
||||
Also, what happens when a variable with the function travels to another place of the code and is called from there -- will it get access to outer variables in the new place?
|
||||
Also, what happens when a function travels to another place of the code and is called from there -- does it get access to outer variables in the new place?
|
||||
|
||||
There is no general programming answer for these questions. Different languages behave differently. Here we'll cover JavaScript.
|
||||
Different languages behave differently here, in this chapter we cover JavaScript.
|
||||
|
||||
[cut]
|
||||
|
||||
## A couple of questions
|
||||
|
||||
Let's formulate two questions for the seed, and then study internal mechanics piece-by-piece, so that you will be able to answer them and have no problems with more complex ones in the future.
|
||||
Let's formulate two questions for the seed, and then study internal mechanics piece-by-piece, so that you'll be able to answer these questions and more complex ones in the future.
|
||||
|
||||
1. The function `sayHi` uses an external variable `name`. The variable changes before function runs. Will it pick up a new variant?
|
||||
1. The function `sayHi` uses an external variable `name`. When the function runs, which value of these two it's going to use?
|
||||
|
||||
```js
|
||||
let name = "John";
|
||||
|
@ -33,7 +33,7 @@ Let's formulate two questions for the seed, and then study internal mechanics pi
|
|||
*/!*
|
||||
```
|
||||
|
||||
Such situations are common in both browser and server-side development. A function may be scheduled to execute later than it is created: on user action or after a network request etc.
|
||||
Such situations are common in both browser and server-side development. A function may be scheduled to execute later than it is created, for instance after a user action or a network request.
|
||||
|
||||
So, the question is: does it pick up latest changes?
|
||||
|
||||
|
@ -86,7 +86,9 @@ Here's the bigger picture of how `let` variables work:
|
|||
|
||||

|
||||
|
||||
1. When a script starts, the Lexical Environment is empty.
|
||||
Rectangles at the right side demonstrate how the global Lexical Environment changes during the execution:
|
||||
|
||||
1. When the script starts, the Lexical Environment is empty.
|
||||
2. The `let phrase` definition appears. Now it initially has no value, so `undefined` is stored.
|
||||
3. The `phrase` is assigned.
|
||||
4. The `phrase` changes the value.
|
||||
|
@ -100,19 +102,20 @@ To summarize:
|
|||
|
||||
### Function Declaration
|
||||
|
||||
Function Declarations are special. Unlike `let` variables, they are processed not when the execution reaches them, but much earlier: when a Lexical Environment is created. For the global Lexical Environment, it means the moment when the script is started.
|
||||
Function Declarations are special. Unlike `let` variables, they are processed not when the execution reaches them, but when a Lexical Environment is created. For the global Lexical Environment, it means the moment when the script is started.
|
||||
|
||||
...And that is why we can call a function declaration before it is defined, like `say` in the code below:
|
||||
...And that is why we can call a function declaration before it is defined.
|
||||
|
||||
The code below demonstrates that the Lexical Environment is non-empty from the beginning. It has `say`, because that's a Function Declaration. And later it gets `phrase`, declared with `let`:
|
||||
|
||||

|
||||
|
||||
Here we have `say` function defined from the beginning of the script, and `let` appears a bit later.
|
||||
|
||||
### Inner and outer Lexical Environment
|
||||
|
||||
Now we have a call to `say()` accessing the outer variables let's see the details of what's going on.
|
||||
During the call `say()` uses an outer variable, so let's see the details of what's going on.
|
||||
|
||||
When a function runs, a new function Lexical Environment is created automatically for variables and parameters of the call.
|
||||
First, when a function runs, a new function Lexical Environment is created automatically. That's a general rule for all functions. That Lexical Environment is used to store local variables and parameters of the call.
|
||||
|
||||
<!--
|
||||
```js
|
||||
|
@ -126,36 +129,35 @@ say("John"); // Hello, John
|
|||
```
|
||||
-->
|
||||
|
||||
Here's the picture for `say("John")`:
|
||||
Here's the picture of Lexical Environments when the execution is inside `say("John")`, at the line labelled with an arrow:
|
||||
|
||||

|
||||
|
||||
The `outer` reference points to the environment outside of the function: the global one.
|
||||
During the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global):
|
||||
|
||||
So during the function call we have two Lexical Environments: the inner (for the function call) and the outer (global):
|
||||
- The inner Lexical Environment corresponds to the current `say` execution. It has a single variable: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`.
|
||||
- The outer Lexical Environment is the global Lexical Environment.
|
||||
|
||||
- The inner Lexical Environment corresponds to the current `say` execution. It has a single variable: `name`.
|
||||
- The outer Lexical Environment is the "right outside" of the function declaration. Here the function is defined in the global Lexical Environment, so this is it.
|
||||
The inner Lexical Environment one has the `outer` reference to the outer one.
|
||||
|
||||
**When a code wants to access a variable -- it is first searched in the inner Lexical Environment, then in the outer one, then the more outer one and so on until the end of the chain.**
|
||||
|
||||
**When a code wants to access a variable -- it is first searched in the inner Lexical Environment, then in the outer one, and further until the end of the chain.**
|
||||
If a variable is not found anywhere, that's an error in strict mode. Without `use strict`, an assignment to an undefined variable creates a new global variable, for backwards compatibility.
|
||||
|
||||
If a variable is not found anywhere, that's an error in strict mode (without `use strict` an assignment to an undefined variable is technically possible, but that's a legacy feature, not recommended).
|
||||
Let's see how the search proceeds in our example:
|
||||
|
||||
Let's see what it means for our example:
|
||||
|
||||
- When the `alert` inside `say` wants to access `name`, it is found immediately in the function Lexical Environment.
|
||||
- When the code wants to access `phrase`, then there is no `phrase` locally, so follows the `outer` reference and finds it globally.
|
||||
- When the `alert` inside `say` wants to access `name`, it finds it immediately in the function Lexical Environment.
|
||||
- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the `outer` reference and finds it globally.
|
||||
|
||||

|
||||
|
||||
Now we can give the answer to the first seed question.
|
||||
Now we can give the answer to the first seed question from the beginning of the chapter.
|
||||
|
||||
**A function sees external variables as they are now.**
|
||||
**A function gets outer variables as they are now, the most recent values.**
|
||||
|
||||
That's because of the described mechanism. Old variable values are not saved anywhere. When a function wants them, it takes the current values from its own or an outer Lexical Environment.
|
||||
|
||||
So the answer is, of course, `Pete`:
|
||||
So the answer here is `Pete`:
|
||||
|
||||
```js run
|
||||
let name = "John";
|
||||
|
@ -186,7 +188,7 @@ And if a function is called multiple times, then each invocation will have its o
|
|||
```
|
||||
|
||||
```smart header="Lexical Environment is a specification object"
|
||||
"Lexical Environment" is a specification object. We can't get this object in our code and manipulate it directly. JavaScript engines also may optimize it, discard variables that are unused in the code and perform other stuff.
|
||||
"Lexical Environment" is a specification object. We can't get this object in our code and manipulate it directly. JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks.
|
||||
```
|
||||
|
||||
|
||||
|
@ -212,11 +214,11 @@ function sayHiBye(firstName, lastName) {
|
|||
}
|
||||
```
|
||||
|
||||
Here the *nested* function `getFullName()` is made for convenience. It has access to outer variables and so can return the full name.
|
||||
Here the *nested* function `getFullName()` is made for convenience. It can access the outer variables and so can return the full name.
|
||||
|
||||
What's more interesting, a nested function can be returned: as a property of an object or as a result by itself. And then used somewhere else. But no matter where, it still keeps the access to the same outer variables.
|
||||
What's more interesting, a nested function can be returned: as a property of a new object (if the outer function creates and returns it) or as a result by itself. And then used somewhere else. No matter where, it still keeps the access to the same outer variables.
|
||||
|
||||
An example with a constructor function (from the chapter <info:constructor-new>):
|
||||
An example with the constructor function (see the chapter <info:constructor-new>):
|
||||
|
||||
```js run
|
||||
// constructor function returns a new object
|
||||
|
@ -250,7 +252,9 @@ alert( counter() ); // 1
|
|||
alert( counter() ); // 2
|
||||
```
|
||||
|
||||
We'll continue on with `makeCounter` example. It creates the "counter" function that returns the next number on each invocation. Despite being simple, such code structure still has practical applications, for instance, a [pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator).
|
||||
Let's go on with the `makeCounter` example. It creates the "counter" function that returns the next number on each invocation. Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator), and more. So the example is not exactly "artificial".
|
||||
|
||||
How the counter works?
|
||||
|
||||
When the inner function runs, the variable in `count++` is searched from inside out.
|
||||
|
||||
|
@ -262,12 +266,12 @@ For the example above, the order will be:
|
|||
2. The variables of the outer function.
|
||||
3. ...And further until it reaches globals.
|
||||
|
||||
As a rule, if an outer variable is modified, then it is changed on the place where it is found. So `count++` finds the outer variable and increases it "at place" (as a property of the corresponding Environment Record) every time.
|
||||
In that example `count` is found on the step `2`. When an outer variable is modified, it's changed where it's found. So `count++` finds the outer variable and increases it in the Lexical Environment where it belongs. Like if we had `let count = 1`.
|
||||
|
||||
The questions may arise:
|
||||
Here's a couple of questions:
|
||||
|
||||
1. Can we somehow reset the counter from the outer code?
|
||||
2. What if we call `makeCounter()` multiple times -- are the resulting `counter` functions independent or they share the same `count`?
|
||||
1. Can we somehow reset the `counter` from the code that doesn't belong to `makeCounter`? E.g. after `alert` calls in the example above.
|
||||
2. If we call `makeCounter()` multiple times -- it returns many `counter` functions. Are they independent or do they share the same `count`?
|
||||
|
||||
Try to answer them before going on reading.
|
||||
|
||||
|
@ -275,52 +279,66 @@ Try to answer them before going on reading.
|
|||
|
||||
Okay, here we go with the answers.
|
||||
|
||||
1. There is no way. The `counter` is a local function variable, we can't access it from outside.
|
||||
1. There is no way. The `counter` is a local function variable, we can't access it from the outside.
|
||||
2. For every call to `makeCounter()` a new function Lexical Environment is created, with its own `counter`. So the resulting `counter` functions are independent.
|
||||
|
||||
Probably, the situation with outer variables is quite clear for you as of now. But my teaching experience shows that still there are problems sometimes. And in more complex situations a solid in-depth understanding of internals may be needed. So here you go.
|
||||
Here's the demo:
|
||||
|
||||
```js run
|
||||
function makeCounter() {
|
||||
let count = 0;
|
||||
return function() {
|
||||
return count++;
|
||||
};
|
||||
}
|
||||
|
||||
let counter1 = makeCounter();
|
||||
let counter2 = makeCounter();
|
||||
|
||||
alert( counter1() ); // 0
|
||||
alert( counter1() ); // 1
|
||||
|
||||
alert( counter2() ); // 0 (independant)
|
||||
```
|
||||
|
||||
|
||||
Probably, the situation with outer variables is quite clear for you as of now. But in more complex situations a deeper understanding of internals may be required. So here you go.
|
||||
|
||||
## Environments in detail
|
||||
|
||||
For a more in-depth understanding, this section elaborates `makeCounter` example in detail, adding some missing pieces.
|
||||
|
||||
Here's what's going on step-by-step:
|
||||
For a more in-depth understanding, here's what's going on in the `makeCounter` example step-by-step, down to the nuts and bolts:
|
||||
|
||||
1. When the script has just started, there is only global Lexical Environment:
|
||||
|
||||

|
||||
|
||||
At this moment there is only `makeCounter` function. And it did not run yet.
|
||||
At that starting moment there is only `makeCounter` function, because it's a Function Declaration. It did not run yet.
|
||||
|
||||
Now let's add one piece that we didn't cover yet.
|
||||
|
||||
All functions "on birth" receive a hidden property `[[Environment]]` with the reference to the Lexical Environment of their creation. So a function kind of remembers where it was made. In the future, when the function runs, `[[Environment]]` is used as the outer lexical reference.
|
||||
All functions "on birth" receive a hidden property `[[Environment]]` with the reference to the Lexical Environment of their creation. We didn't talk about it yet, but technically that's a way how the function knows where it was made.
|
||||
|
||||
Here, `makeCounter` is created in the global Lexical Environment, so `[[Environment]]` keeps the reference to it.
|
||||
|
||||
2. Then the code runs on, and the call to `makeCounter()` is performed. Here's the state when we are at the first line inside `makeCounter()`:
|
||||
2. Then the code runs on, and the call to `makeCounter()` is performed. Here's the picture for the moment when the execution is on the first line inside `makeCounter()`:
|
||||
|
||||

|
||||
|
||||
The Lexical Environment for the `makeCounter()` call is created.
|
||||
At the moment of the call of `makeCounter()`, the Lexical Environment is created, to hold its variables and arguments.
|
||||
|
||||
As all Lexical Environments, it stores two things:
|
||||
- Environment Record with local variables, in our case `count` is the only local variable (after the line with `let count` is processed).
|
||||
- The outer lexical reference, which is set using `[[Environment]]` of the function. Here `[[Environment]]` of `makeCounter` references the global Lexical Environment.
|
||||
1. An Environment Record with local variables. In our case `count` is the only local variable (appears in it when the line with `let count` is executed).
|
||||
2. The outer lexical reference, which is set to `[[Environment]]` of the function. Here `[[Environment]]` of `makeCounter` references the global Lexical Environment.
|
||||
|
||||
So, now we have two Lexical Environments: the first one is global, the second one is for the current `makeCounter` call, with the outer reference to global.
|
||||
|
||||
3. During the execution of `makeCounter()`, a tiny nested function is created.
|
||||
|
||||
It doesn't matter how the function is created: using Function Declaration or Function Expression or in an object literal as a method. All functions get the `[[Environment]]` property that references the Lexical Environment where they are made.
|
||||
It doesn't matter whether the function is created using Function Declaration or Function Expression. All functions get the `[[Environment]]` property that references the Lexical Environment where they were made.
|
||||
|
||||
For our new nested function that is the current Lexical Environment of `makeCounter()`:
|
||||
|
||||

|
||||
|
||||
Please note that at the inner function was created, but not yet called. The code inside `function() { return count++; }` is not running.
|
||||
|
||||
So we still have two Lexical Environments. And a function which has `[[Environment]]` referencing to the inner one of them.
|
||||
Please note that on this step the inner function was created, but not yet called. The code inside `function() { return count++; }` is not running, we're going to return it.
|
||||
|
||||
4. As the execution goes on, the call to `makeCounter()` finishes, and the result (the tiny nested function) is assigned to the global variable `counter`:
|
||||
|
||||
|
@ -328,7 +346,7 @@ Here's what's going on step-by-step:
|
|||
|
||||
That function has only one line: `return count++`, that will be executed when we run it.
|
||||
|
||||
5. When the `counter()` is called, an "empty" Lexical Environment is created for it. It has no local variables by itself. But the `[[Environment]]` of `counter` is used for the outer reference, so it has access to the variables of the former `makeCounter()` call, where it was created:
|
||||
5. When the `counter()` is called, an "empty" Lexical Environment is created for it. It has no local variables by itself. But the `[[Environment]]` of `counter` is used as the outer reference for it, so it has access to the variables of the former `makeCounter()` call, where it was created:
|
||||
|
||||

|
||||
|
||||
|
@ -372,9 +390,11 @@ When on an interview a frontend developer gets a question about "what's a closur
|
|||
|
||||
The examples above concentrated on functions. But Lexical Environments also exist for code blocks `{...}`.
|
||||
|
||||
They are created when a code block runs and contain block-local variables.
|
||||
They are created when a code block runs and contain block-local variables. Here's a couple of examples.
|
||||
|
||||
In the example below, when the execution goes into `if` block, the new Lexical Environment is created for it:
|
||||
## If
|
||||
|
||||
In the example below, when the execution goes into `if` block, the new "if-only" Lexical Environment is created for it:
|
||||
|
||||
<!--
|
||||
```js run
|
||||
|
@ -392,24 +412,36 @@ alert(user); // Error, can't see such variable!
|
|||
|
||||

|
||||
|
||||
The new Lexical Environment gets the enclosing one as the outer reference, so `phrase` can be found. But all variables and Function Expressions declared inside `if`, will reside in that Lexical Environment.
|
||||
The new Lexical Environment gets the enclosing one as the outer reference, so `phrase` can be found. But all variables and Function Expressions declared inside `if` reside in that Lexical Environment and can't be seen from the outside.
|
||||
|
||||
After `if` finishes, the `alert` below won't see the `user`.
|
||||
For instance, after `if` finishes, the `alert` below won't see the `user`, hence the error.
|
||||
|
||||
For a loop, every run has a separate Lexical Environment. The loop variable is its part:
|
||||
## For, while
|
||||
|
||||
For a loop, every run has a separate Lexical Environment. If the variable is declared in `for`, then it's also local to that Lexical Environment:
|
||||
|
||||
```js run
|
||||
for(let i = 0; i < 10; i++) {
|
||||
// Each loop has its own Lexical Environment
|
||||
// {i: value}
|
||||
}
|
||||
|
||||
alert(i); // Error, no such variable
|
||||
```
|
||||
|
||||
We also can use a "bare" code block to isolate variables.
|
||||
That's actually an exception, because `let i` is visually outside of `{...}`. But in fact each run of the loop has its own Lexical Environment with the current `i` in it.
|
||||
|
||||
For instance, in-browser all scripts share the same global area. So if we create a global variable in one script, it becomes available to others. That may be a source of conflicts if two scripts use the same variable name and overwrite each other.
|
||||
After the loop, `i` is not visible.
|
||||
|
||||
If we don't want that, we can use a code block to isolate the whole script or an area in it:
|
||||
### Code blocks
|
||||
|
||||
We also can use a "bare" code block `{…}` to isolate variables into a "local scope".
|
||||
|
||||
For instance, in a web browser all scripts share the same global area. So if we create a global variable in one script, it becomes available to others. But that become a source of conflicts if two scripts use the same variable name and overwrite each other.
|
||||
|
||||
That may happen if the variable name is a widespread word, and script authors are unaware of each other.
|
||||
|
||||
If we'd like to evade that, we can use a code block to isolate the whole script or an area in it:
|
||||
|
||||
```js run
|
||||
{
|
||||
|
@ -423,7 +455,11 @@ If we don't want that, we can use a code block to isolate the whole script or an
|
|||
alert(message); // Error: message is not defined
|
||||
```
|
||||
|
||||
In old scripts, you can find immediately-invoked function expressions (abbreviated as IIFE) used for this purpose.
|
||||
The code outside of the block (or inside another script) doesn't see variables in it, because a code block has its own Lexical Environment.
|
||||
|
||||
### IIFE
|
||||
|
||||
In old scripts, one can find so-called "immediately-invoked function expressions" (abbreviated as IIFE) used for this purpose.
|
||||
|
||||
They look like this:
|
||||
|
||||
|
@ -461,7 +497,7 @@ function go() {
|
|||
}(); // <-- can't call Function Declaration immediately
|
||||
```
|
||||
|
||||
So the brackets are needed to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression. Needs no name and can be called immediately.
|
||||
...So the brackets are needed to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression. Needs no name and can be called immediately.
|
||||
|
||||
There are other ways to tell JavaScript that we mean Function Expression:
|
||||
|
||||
|
@ -502,15 +538,15 @@ Lexical Environment objects that we've been talking about are subjects to same m
|
|||
f();
|
||||
```
|
||||
|
||||
It's obvious that both values are unaccessible after the end of `f()`. Formally, there are no references to Lexical Environment object with them, so it gets cleaned up.
|
||||
Here two values are technically the properties of the Lexical Environment. But after `f()` finishes that Lexical Environment becomes unreachable, so it's deleted from the memory.
|
||||
|
||||
- But if there's a nested function that is still reachable after the end of `f`, then its `[[Environment]]` reference keeps the outer lexical environment alive as well:
|
||||
- ...But if there's a nested function that is still reachable after the end of `f`, then its `[[Environment]]` reference keeps the outer lexical environment alive as well:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value = 123;
|
||||
|
||||
function g() {}
|
||||
function g() { alert(value); }
|
||||
|
||||
*!*
|
||||
return g;
|
||||
|
@ -520,13 +556,13 @@ Lexical Environment objects that we've been talking about are subjects to same m
|
|||
let g = f(); // g is reachable, and keeps the outer lexical environment in memory
|
||||
```
|
||||
|
||||
- If `f()` is called many times, and resulting functions are saved, then the corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
|
||||
- Please note that if `f()` is called many times, and resulting functions are saved, then the corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value = Math.random();
|
||||
|
||||
return function() {};
|
||||
return function() { alert(value); };
|
||||
}
|
||||
|
||||
// 3 functions in array, every of them links to LexicalEnvrironment
|
||||
|
@ -534,13 +570,14 @@ Lexical Environment objects that we've been talking about are subjects to same m
|
|||
// LE LE LE
|
||||
let arr = [f(), f(), f()];
|
||||
```
|
||||
- A Lexical Environment object dies when no nested functions remain that reference it. In the code below, after `g` becomes unreachable, it dies with it:
|
||||
|
||||
- A Lexical Environment object dies when it becomes unreachable. That is: when no nested functions remain that reference it. In the code below, after `g` becomes unreachable, the `value` is also cleaned from the memory;
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value = 123;
|
||||
|
||||
function g() {}
|
||||
function g() { alert(value); }
|
||||
|
||||
return g;
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 22 KiB |