refactor promise, geneerators, global object

This commit is contained in:
Ilya Kantor 2019-02-28 00:56:39 +03:00
parent be9c5a7b5f
commit 2ee2751216
69 changed files with 900 additions and 643 deletions

View file

@ -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:
![lexical environment](lexical-environment-simple.png)
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.
![lexical environment lookup](lexical-environment-simple-lookup.png)
@ -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"
![](lexenv-if.png)
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