refactor promise, geneerators, global object
This commit is contained in:
parent
be9c5a7b5f
commit
2ee2751216
69 changed files with 900 additions and 643 deletions
|
@ -70,7 +70,7 @@ The Lexical Environment object consists of two parts:
|
|||
1. *Environment Record* -- an object that has all local variables as its properties (and some other information like the value of `this`).
|
||||
2. A reference to the *outer lexical environment*, usually the one associated with the code lexically right outside of it (outside of the current curly brackets).
|
||||
|
||||
So, a "variable" is just a property of the special internal object, Environment Record. "To get or change a variable" means "to get or change a property of the Lexical Environment".
|
||||
**So, a "variable" is just a property of the special internal object, Environment Record. "To get or change a variable" means "to get or change a property of that object".**
|
||||
|
||||
For instance, in this simple code, there is only one Lexical Environment:
|
||||
|
||||
|
@ -100,7 +100,11 @@ To summarize:
|
|||
|
||||
### Function Declaration
|
||||
|
||||
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.
|
||||
Till now, we only observed variables. Now enter Function Declarations.
|
||||
|
||||
**Unlike `let` variables, they are fully initialized not when the execution reaches them, but earlier, when a Lexical Environment is created.**
|
||||
|
||||
For top-level functions, it means the moment when the script is started.
|
||||
|
||||
That is why we can call a function declaration before it is defined.
|
||||
|
||||
|
@ -111,10 +115,14 @@ The code below demonstrates that the Lexical Environment is non-empty from the b
|
|||
|
||||
### Inner and outer Lexical Environment
|
||||
|
||||
During the call, `say()` uses an outer variable, so let's look at the details of what's going on.
|
||||
Now let's go on and explore what happens when a function accesses an outer variable.
|
||||
|
||||
During the call, `say()` uses the outer variable `phrase`, let's look at the details of what's going on.
|
||||
|
||||
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.
|
||||
|
||||
For instance, for `say("John")`, it looks like this (the execution is at the line, labelled with an arrow):
|
||||
|
||||
<!--
|
||||
```js
|
||||
let phrase = "Hello";
|
||||
|
@ -126,25 +134,27 @@ First, when a function runs, a new function Lexical Environment is created autom
|
|||
say("John"); // Hello, John
|
||||
```-->
|
||||
|
||||
Here's the picture of Lexical Environments when the execution is inside `say("John")`, at the line labelled with an arrow:
|
||||
|
||||

|
||||
|
||||
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 one (for the function call) and the outer one (global):
|
||||
|
||||
- The inner Lexical Environment corresponds to the current execution of `say`. It has a single variable: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`.
|
||||
- The inner Lexical Environment corresponds to the current execution of `say`.
|
||||
|
||||
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 has the `outer` reference to the outer one.
|
||||
It has `phrase` and the function itself.
|
||||
|
||||
**When code wants to access a variable -- it is first searched for in the inner Lexical Environment, then in the outer one, then the more outer one and so on until the end of the chain.**
|
||||
The inner Lexical Environment has a reference to the outer one.
|
||||
|
||||
**When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on 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.
|
||||
|
||||
Let's see how the search proceeds in our example:
|
||||
|
||||
- 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.
|
||||
- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the reference to the enclosing Lexical Environment and finds it there.
|
||||
|
||||

|
||||
|
||||
|
@ -211,11 +221,11 @@ function sayHiBye(firstName, lastName) {
|
|||
}
|
||||
```
|
||||
|
||||
Here the *nested* function `getFullName()` is made for convenience. It can access the 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. Nested functions are quite common in Javascript.
|
||||
|
||||
What's more interesting, a nested function can be returned: either as a property of a new object (if the outer function creates an object with methods) or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables.
|
||||
What's much more interesting, a nested function can be returned: either as a property of a new object (if the outer function creates an object with methods) or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables.
|
||||
|
||||
An example with the constructor function (see the chapter <info:constructor-new>):
|
||||
For instance, here the nested function is assigned to the new object by the [constructor function](info:constructor-new):
|
||||
|
||||
```js run
|
||||
// constructor function returns a new object
|
||||
|
@ -228,10 +238,10 @@ function User(name) {
|
|||
}
|
||||
|
||||
let user = new User("John");
|
||||
user.sayHi(); // the method code has access to the outer "name"
|
||||
user.sayHi(); // the method "sayHi" code has access to the outer "name"
|
||||
```
|
||||
|
||||
An example with returning a function:
|
||||
And here we just create and return a "counting" function:
|
||||
|
||||
```js run
|
||||
function makeCounter() {
|
||||
|
@ -249,7 +259,7 @@ alert( counter() ); // 1
|
|||
alert( counter() ); // 2
|
||||
```
|
||||
|
||||
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 as artificial as it may appear.
|
||||
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.
|
||||
|
||||
How does the counter work internally?
|
||||
|
||||
|
@ -303,9 +313,11 @@ Hopefully, the situation with outer variables is quite clear for you now. But in
|
|||
|
||||
## Environments in detail
|
||||
|
||||
Now that you understand how closures work generally, we can descend to the very nuts and bolts.
|
||||
Now that you understand how closures work generally, that's already very good.
|
||||
|
||||
Here's what's going on in the `makeCounter` example step-by-step, follow it to make sure that you understand everything. Please note the additional `[[Environment]]` property that we didn't cover yet.
|
||||
Here's what's going on in the `makeCounter` example step-by-step, follow it to make sure that you know things in the very detail.
|
||||
|
||||
Please note the additional `[[Environment]]` property is covered here. We didn't mention it before for simplicity.
|
||||
|
||||
1. When the script has just started, there is only global Lexical Environment:
|
||||
|
||||
|
@ -313,7 +325,7 @@ Here's what's going on in the `makeCounter` example step-by-step, follow it to m
|
|||
|
||||
At that starting moment there is only `makeCounter` function, because it's a Function Declaration. It did not run yet.
|
||||
|
||||
All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation. We didn't talk about it yet, but that's how the function knows where it was made.
|
||||
**All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation.** We didn't talk about it yet, but that's how the function knows where it was made.
|
||||
|
||||
Here, `makeCounter` is created in the global Lexical Environment, so `[[Environment]]` keeps a reference to it.
|
||||
|
||||
|
@ -389,13 +401,13 @@ When on an interview, a frontend developer gets a question about "what's a closu
|
|||
|
||||
## Code blocks and loops, IIFE
|
||||
|
||||
The examples above concentrated on functions. But Lexical Environments also exist for code blocks `{...}`.
|
||||
The examples above concentrated on functions. But a Lexical Environment exists for any code block `{...}`.
|
||||
|
||||
They are created when a code block runs and contain block-local variables. Here are a couple of examples.
|
||||
A Lexical Environment is created when a code block runs and contains block-local variables. Here are a couple of examples.
|
||||
|
||||
## If
|
||||
### If
|
||||
|
||||
In the example below, when the execution goes into `if` block, the new "if-only" Lexical Environment is created for it:
|
||||
In the example below, the `user` variable exists only in the `if` block:
|
||||
|
||||
<!--
|
||||
```js run
|
||||
|
@ -412,11 +424,13 @@ In the example below, when the execution goes into `if` block, the new "if-only"
|
|||
|
||||

|
||||
|
||||
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.
|
||||
When the execution gets into the `if` block, the new "if-only" Lexical Environment is created for it.
|
||||
|
||||
It has the reference to the outer one, 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.
|
||||
|
||||
For instance, after `if` finishes, the `alert` below won't see the `user`, hence the error.
|
||||
|
||||
## For, while
|
||||
### For, while
|
||||
|
||||
For a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for`, then it's also local to that Lexical Environment:
|
||||
|
||||
|
@ -429,9 +443,9 @@ for (let i = 0; i < 10; i++) {
|
|||
alert(i); // Error, no such variable
|
||||
```
|
||||
|
||||
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.
|
||||
Please note: `let i` is visually outside of `{...}`. The `for` construct is somewhat special here: each iteration of the loop has its own Lexical Environment with the current `i` in it.
|
||||
|
||||
After the loop, `i` is not visible.
|
||||
Again, similarly to `if`, after the loop `i` is not visible.
|
||||
|
||||
### Code blocks
|
||||
|
||||
|
@ -459,9 +473,13 @@ The code outside of the block (or inside another script) doesn't see variables i
|
|||
|
||||
### IIFE
|
||||
|
||||
In old scripts, one can find so-called "immediately-invoked function expressions" (abbreviated as IIFE) used for this purpose.
|
||||
In the past, there were no block-level lexical environment in Javascript.
|
||||
|
||||
They look like this:
|
||||
So programmers had to invent something. And what they did is called "immediately-invoked function expressions" (abbreviated as IIFE).
|
||||
|
||||
That's not a thing we should use nowadays, but you can find them in old scripts, so it's better to understand them.
|
||||
|
||||
IIFE looks like this:
|
||||
|
||||
```js run
|
||||
(function() {
|
||||
|
@ -475,11 +493,11 @@ They look like this:
|
|||
|
||||
Here a Function Expression is created and immediately called. So the code executes right away and has its own private variables.
|
||||
|
||||
The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so there will be an error:
|
||||
The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error:
|
||||
|
||||
```js run
|
||||
// Error: Unexpected token (
|
||||
function() { // <-- JavaScript cannot find function name, meets ( and gives error
|
||||
// Try to declare and immediately call a function
|
||||
function() { // <-- Error: Unexpected token (
|
||||
|
||||
let message = "Hello";
|
||||
|
||||
|
@ -488,7 +506,7 @@ function() { // <-- JavaScript cannot find function name, meets ( and gives erro
|
|||
}();
|
||||
```
|
||||
|
||||
We can say "okay, let it be so Function Declaration, let's add a name", but it won't work. JavaScript does not allow Function Declarations to be called immediately:
|
||||
Even if we say: "okay, let's add a name", that won't work, as JavaScript does not allow Function Declarations to be called immediately:
|
||||
|
||||
```js run
|
||||
// syntax error because of parentheses below
|
||||
|
@ -497,9 +515,9 @@ function go() {
|
|||
}(); // <-- can't call Function Declaration immediately
|
||||
```
|
||||
|
||||
So, parenthesis are needed to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression. It needs no name and can be called immediately.
|
||||
So, parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression: it needs no name and can be called immediately.
|
||||
|
||||
There are other ways to tell JavaScript that we mean Function Expression:
|
||||
There exist other ways besides parentheses to tell JavaScript that we mean a Function Expression:
|
||||
|
||||
```js run
|
||||
// Ways to create IIFE
|
||||
|
@ -525,68 +543,68 @@ In all the above cases we declare a Function Expression and run it immediately.
|
|||
|
||||
## Garbage collection
|
||||
|
||||
Lexical Environment objects that we've been talking about are subject to the same memory management rules as regular values.
|
||||
Usually, a Lexical Environment is cleaned up and deleted after the function run. For instance:
|
||||
|
||||
- Usually, Lexical Environment is cleaned up after the function run. For instance:
|
||||
```js
|
||||
function f() {
|
||||
let value1 = 123;
|
||||
let value2 = 456;
|
||||
}
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value1 = 123;
|
||||
let value2 = 456;
|
||||
}
|
||||
f();
|
||||
```
|
||||
|
||||
f();
|
||||
```
|
||||
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.
|
||||
|
||||
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;
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value = 123;
|
||||
function g() { alert(value); }
|
||||
|
||||
function g() { alert(value); }
|
||||
*!*
|
||||
return g;
|
||||
*/!*
|
||||
}
|
||||
|
||||
*!*
|
||||
return g;
|
||||
*/!*
|
||||
}
|
||||
let g = f(); // g is reachable, and keeps the outer lexical environment in memory
|
||||
```
|
||||
|
||||
let g = f(); // g is reachable, and keeps the outer lexical environment in memory
|
||||
```
|
||||
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:
|
||||
|
||||
- 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();
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value = Math.random();
|
||||
return function() { alert(value); };
|
||||
}
|
||||
|
||||
return function() { alert(value); };
|
||||
}
|
||||
// 3 functions in array, every one of them links to Lexical Environment
|
||||
// from the corresponding f() run
|
||||
// LE LE LE
|
||||
let arr = [f(), f(), f()];
|
||||
```
|
||||
|
||||
// 3 functions in array, every one of them links to Lexical Environment
|
||||
// from the corresponding f() run
|
||||
// LE LE LE
|
||||
let arr = [f(), f(), f()];
|
||||
```
|
||||
A Lexical Environment object dies when it becomes unreachable (just like any other object). In other words, it exists only while there's at least one nested function referencing it.
|
||||
|
||||
- A Lexical Environment object dies when it becomes unreachable: when no nested functions remain that reference it. In the code below, after `g` becomes unreachable, the `value` is also cleaned from memory;
|
||||
In the code below, after `g` becomes unreachable, enclosing Lexical Environment (and hence the `value`) is cleaned from memory;
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value = 123;
|
||||
```js
|
||||
function f() {
|
||||
let value = 123;
|
||||
|
||||
function g() { alert(value); }
|
||||
function g() { alert(value); }
|
||||
|
||||
return g;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
let g = f(); // while g is alive
|
||||
// there corresponding Lexical Environment lives
|
||||
let g = f(); // while g is alive
|
||||
// there corresponding Lexical Environment lives
|
||||
|
||||
g = null; // ...and now the memory is cleaned up
|
||||
```
|
||||
g = null; // ...and now the memory is cleaned up
|
||||
```
|
||||
|
||||
### Real-life optimizations
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue