Merge branch 'master' into patch-2
|
@ -266,7 +266,7 @@ There may be many occurrences of `return` in a single function. For instance:
|
|||
|
||||
```js run
|
||||
function checkAge(age) {
|
||||
if (age > 18) {
|
||||
if (age >= 18) {
|
||||
*!*
|
||||
return true;
|
||||
*/!*
|
||||
|
|
|
@ -159,8 +159,8 @@ We can select one of two ways to organize the test here:
|
|||
assert.equal(pow(2, 3), 8);
|
||||
});
|
||||
|
||||
it("3 raised to power 3 is 27", function() {
|
||||
assert.equal(pow(3, 3), 27);
|
||||
it("3 raised to power 4 is 81", function() {
|
||||
assert.equal(pow(3, 4), 81);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -182,7 +182,7 @@ The result:
|
|||
|
||||
[iframe height=250 src="pow-2" edit border="1"]
|
||||
|
||||
As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `27`.
|
||||
As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `81`.
|
||||
|
||||
## Improving the implementation
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ describe("pow", function() {
|
|||
assert.equal(pow(2, 3), 8);
|
||||
});
|
||||
|
||||
it("3 raised to power 3 is 27", function() {
|
||||
assert.equal(pow(3, 3), 27);
|
||||
it("3 raised to power 4 is 81", function() {
|
||||
assert.equal(pow(3, 4), 81);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ Let's look at the key distinctions between primitives and objects.
|
|||
A primitive
|
||||
|
||||
- Is a value of a primitive type.
|
||||
- There are 6 primitive types: `string`, `number`, `boolean`, `symbol`, `null` and `undefined`.
|
||||
- There are 7 primitive types: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` and `undefined`.
|
||||
|
||||
An object
|
||||
|
||||
|
|
|
@ -413,7 +413,7 @@ There are more functions and constants in `Math` object, including trigonometry,
|
|||
To write numbers with many zeroes:
|
||||
|
||||
- Append `"e"` with the zeroes count to the number. Like: `123e6` is the same as `123` with 6 zeroes `123000000`.
|
||||
- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. E.g. `123e-6` means `0.000123` (`123` millionth).
|
||||
- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. E.g. `123e-6` means `0.000123` (`123` millionths).
|
||||
|
||||
For different numeral systems:
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
function groupById(array) {
|
||||
return array.reduce((obj, value) => {
|
||||
obj[value.id] = value;
|
||||
return obj;
|
||||
}, {})
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
describe("groupById", function() {
|
||||
|
||||
it("creates an object grouped by id", function() {
|
||||
let users = [
|
||||
{id: 'john', name: "John Smith", age: 20},
|
||||
{id: 'ann', name: "Ann Smith", age: 24},
|
||||
{id: 'pete', name: "Pete Peterson", age: 31},
|
||||
];
|
||||
|
||||
assert.deepEqual(groupById(users), {
|
||||
john: {id: 'john', name: "John Smith", age: 20}
|
||||
ann: {id: 'ann', name: "Ann Smith", age: 24},
|
||||
pete: {id: 'pete', name: "Pete Peterson", age: 31},
|
||||
});
|
||||
});
|
||||
|
||||
it("works with an empty array", function() {
|
||||
assert.deepEqual(groupById(users), {});
|
||||
});
|
||||
});
|
37
1-js/05-data-types/05-array-methods/12-reduce-object/task.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Create keyed object from array
|
||||
|
||||
Let's say we received an array of users in the form `{id:..., name:..., age... }`.
|
||||
|
||||
Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
let users = [
|
||||
{id: 'john', name: "John Smith", age: 20},
|
||||
{id: 'ann', name: "Ann Smith", age: 24},
|
||||
{id: 'pete', name: "Pete Peterson", age: 31},
|
||||
];
|
||||
|
||||
let usersById = groupById(users);
|
||||
|
||||
/*
|
||||
// after the call we have:
|
||||
|
||||
usersById = {
|
||||
john: {id: 'john', name: "John Smith", age: 20}
|
||||
ann: {id: 'ann', name: "Ann Smith", age: 24},
|
||||
pete: {id: 'pete', name: "Pete Peterson", age: 31},
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
Such function is really handy when working with server data.
|
||||
|
||||
In this task we assume that `id` is unique. There may be no two array items with the same `id`.
|
||||
|
||||
Please use array `.reduce` method in the solution.
|
|
@ -431,7 +431,6 @@ By the way, if we ever want to know which elements are compared -- nothing preve
|
|||
|
||||
The algorithm may compare an element with multiple others in the process, but it tries to make as few comparisons as possible.
|
||||
|
||||
|
||||
````smart header="A comparison function may return any number"
|
||||
Actually, a comparison function is only required to return a positive number to say "greater" and a negative number to say "less".
|
||||
|
||||
|
@ -456,6 +455,22 @@ arr.sort( (a, b) => a - b );
|
|||
This works exactly the same as the longer version above.
|
||||
````
|
||||
|
||||
````smart header="Use `localeCompare` for strings"
|
||||
Remember [strings](info:string#correct-comparisons) comparison algorithm? It compares letters by their codes by default.
|
||||
|
||||
For many alphabets, it's better to use `str.localeCompare` method to correctly sort letters, such as `Ö`.
|
||||
|
||||
For example, let's sort a few countries in German:
|
||||
|
||||
```js run
|
||||
let countries = ['Österreich', 'Andorra', 'Vietnam'];
|
||||
|
||||
alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (wrong)
|
||||
|
||||
alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (correct!)
|
||||
```
|
||||
````
|
||||
|
||||
### reverse
|
||||
|
||||
The method [arr.reverse](mdn:js/Array/reverse) reverses the order of elements in `arr`.
|
||||
|
@ -530,7 +545,7 @@ The methods [arr.reduce](mdn:js/Array/reduce) and [arr.reduceRight](mdn:js/Array
|
|||
The syntax is:
|
||||
|
||||
```js
|
||||
let value = arr.reduce(function(previousValue, item, index, array) {
|
||||
let value = arr.reduce(function(accumulator, item, index, array) {
|
||||
// ...
|
||||
}, [initial]);
|
||||
```
|
||||
|
@ -539,14 +554,16 @@ The function is applied to all array elements one after another and "carries on"
|
|||
|
||||
Arguments:
|
||||
|
||||
- `previousValue` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided).
|
||||
- `accumulator` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided).
|
||||
- `item` -- is the current array item.
|
||||
- `index` -- is its position.
|
||||
- `array` -- is the array.
|
||||
|
||||
As function is applied, the result of the previous function call is passed to the next one as the first argument.
|
||||
|
||||
Sounds complicated, but it's not if you think about the first argument as the "accumulator" that stores the combined result of all previous execution. And at the end it becomes the result of `reduce`.
|
||||
So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end it becomes the result of `reduce`.
|
||||
|
||||
Sounds complicated?
|
||||
|
||||
The easiest way to grasp that is by example.
|
||||
|
||||
|
@ -611,7 +628,6 @@ let arr = [];
|
|||
arr.reduce((sum, current) => sum + current);
|
||||
```
|
||||
|
||||
|
||||
So it's advised to always specify the initial value.
|
||||
|
||||
The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Rest parameters and spread operator
|
||||
# Rest parameters and spread syntax
|
||||
|
||||
Many JavaScript built-in functions support an arbitrary number of arguments.
|
||||
|
||||
|
@ -122,7 +122,7 @@ As we remember, arrow functions don't have their own `this`. Now we know they do
|
|||
````
|
||||
|
||||
|
||||
## Spread operator [#spread-operator]
|
||||
## Spread syntax [#spread-syntax]
|
||||
|
||||
We've just seen how to get an array from the list of parameters.
|
||||
|
||||
|
@ -148,7 +148,7 @@ alert( Math.max(arr) ); // NaN
|
|||
|
||||
And surely we can't manually list items in the code `Math.max(arr[0], arr[1], arr[2])`, because we may be unsure how many there are. As our script executes, there could be a lot, or there could be none. And that would get ugly.
|
||||
|
||||
*Spread operator* to the rescue! It looks similar to rest parameters, also using `...`, but does quite the opposite.
|
||||
*Spread syntax* to the rescue! It looks similar to rest parameters, also using `...`, but does quite the opposite.
|
||||
|
||||
When `...arr` is used in the function call, it "expands" an iterable object `arr` into the list of arguments.
|
||||
|
||||
|
@ -169,7 +169,7 @@ let arr2 = [8, 3, -8, 1];
|
|||
alert( Math.max(...arr1, ...arr2) ); // 8
|
||||
```
|
||||
|
||||
We can even combine the spread operator with normal values:
|
||||
We can even combine the spread syntax with normal values:
|
||||
|
||||
|
||||
```js run
|
||||
|
@ -179,7 +179,7 @@ let arr2 = [8, 3, -8, 1];
|
|||
alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25
|
||||
```
|
||||
|
||||
Also, the spread operator can be used to merge arrays:
|
||||
Also, the spread syntax can be used to merge arrays:
|
||||
|
||||
```js run
|
||||
let arr = [3, 5, 1];
|
||||
|
@ -192,9 +192,9 @@ let merged = [0, ...arr, 2, ...arr2];
|
|||
alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2)
|
||||
```
|
||||
|
||||
In the examples above we used an array to demonstrate the spread operator, but any iterable will do.
|
||||
In the examples above we used an array to demonstrate the spread syntax, but any iterable will do.
|
||||
|
||||
For instance, here we use the spread operator to turn the string into array of characters:
|
||||
For instance, here we use the spread syntax to turn the string into array of characters:
|
||||
|
||||
```js run
|
||||
let str = "Hello";
|
||||
|
@ -202,7 +202,7 @@ let str = "Hello";
|
|||
alert( [...str] ); // H,e,l,l,o
|
||||
```
|
||||
|
||||
The spread operator internally uses iterators to gather elements, the same way as `for..of` does.
|
||||
The spread syntax internally uses iterators to gather elements, the same way as `for..of` does.
|
||||
|
||||
So, for a string, `for..of` returns characters and `...str` becomes `"H","e","l","l","o"`. The list of characters is passed to array initializer `[...str]`.
|
||||
|
||||
|
@ -220,24 +220,24 @@ The result is the same as `[...str]`.
|
|||
But there's a subtle difference between `Array.from(obj)` and `[...obj]`:
|
||||
|
||||
- `Array.from` operates on both array-likes and iterables.
|
||||
- The spread operator operates only on iterables.
|
||||
- The spread syntax works only with iterables.
|
||||
|
||||
So, for the task of turning something into an array, `Array.from` tends to be more universal.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
When we see `"..."` in the code, it is either rest parameters or the spread operator.
|
||||
When we see `"..."` in the code, it is either rest parameters or the spread syntax.
|
||||
|
||||
There's an easy way to distinguish between them:
|
||||
|
||||
- When `...` is at the end of function parameters, it's "rest parameters" and gathers the rest of the list of arguments into an array.
|
||||
- When `...` occurs in a function call or alike, it's called a "spread operator" and expands an array into a list.
|
||||
- When `...` occurs in a function call or alike, it's called a "spread syntax" and expands an array into a list.
|
||||
|
||||
Use patterns:
|
||||
|
||||
- Rest parameters are used to create functions that accept any number of arguments.
|
||||
- The spread operator is used to pass an array to functions that normally require a list of many arguments.
|
||||
- The spread syntax is used to pass an array to functions that normally require a list of many arguments.
|
||||
|
||||
Together they help to travel between a list and an array of parameters with ease.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
The answer is: **Pete**.
|
||||
|
||||
A function gets outer variables as they are now, it uses the most recent values.
|
||||
|
||||
Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one.
|
|
@ -0,0 +1,23 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Does a function pickup latest changes?
|
||||
|
||||
The function sayHi uses an external variable name. When the function runs, which value is it going to use?
|
||||
|
||||
```js
|
||||
let name = "John";
|
||||
|
||||
function sayHi() {
|
||||
alert("Hi, " + name);
|
||||
}
|
||||
|
||||
name = "Pete";
|
||||
|
||||
sayHi(); // what will it show: "John" or "Pete"?
|
||||
```
|
||||
|
||||
Such situations are common both in 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 the latest changes?
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
@ -0,0 +1,9 @@
|
|||
The answer is: **Pete**.
|
||||
|
||||
The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference:
|
||||
|
||||

|
||||
|
||||
So, the result is `"Pete"` here.
|
||||
|
||||
But if there were no `let name` in `makeWorker()`, then the search would go outside and take the global variable as we can see from the chain above. In that case the result would be `"John"`.
|
|
@ -0,0 +1,29 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Which variables are available?
|
||||
|
||||
The function `makeWorker` below makes another function and returns it. That new function can be called from somewhere else.
|
||||
|
||||
Will it have access to the outer variables from its creation place, or the invocation place, or both?
|
||||
|
||||
```js
|
||||
function makeWorker() {
|
||||
let name = "Pete";
|
||||
|
||||
return function() {
|
||||
alert(name);
|
||||
};
|
||||
}
|
||||
|
||||
let name = "John";
|
||||
|
||||
// create a function
|
||||
let work = makeWorker();
|
||||
|
||||
// call it
|
||||
work(); // what will it show?
|
||||
```
|
||||
|
||||
Which value it will show? "Pete" or "John"?
|
|
@ -0,0 +1,40 @@
|
|||
The result is: **error**.
|
||||
|
||||
Try running it:
|
||||
|
||||
```js run
|
||||
let x = 1;
|
||||
|
||||
function func() {
|
||||
*!*
|
||||
console.log(x); // ReferenceError: Cannot access 'x' before initialization
|
||||
*/!*
|
||||
let x = 2;
|
||||
}
|
||||
|
||||
func();
|
||||
```
|
||||
|
||||
In this example we can observe the peculiar difference between a "non-existing" and "unitialized" variable.
|
||||
|
||||
As you may have read in the article [](info:closure), a variable starts in the "uninitialized" state from the moment when the execution enters a code block (or a function). And it stays uninitalized until the corresponding `let` statement.
|
||||
|
||||
In other words, a variable technically exists, but can't be used before `let`.
|
||||
|
||||
The code above demonstrates it.
|
||||
|
||||
```js
|
||||
function func() {
|
||||
*!*
|
||||
// the local variable x is known to the engine from the beginning of the function,
|
||||
// but "unitialized" (unusable) until let ("dead zone")
|
||||
// hence the error
|
||||
*/!*
|
||||
|
||||
console.log(x); // ReferenceError: Cannot access 'vx before initialization
|
||||
|
||||
let x = 2;
|
||||
}
|
||||
```
|
||||
|
||||
This zone of temporary unusability of a variable (from the beginning of the code block till `let`) is sometimes called the "dead zone".
|
21
1-js/06-advanced-functions/03-closure/7-let-scope/task.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Is variable visible?
|
||||
|
||||
What will be the result of this code?
|
||||
|
||||
```js
|
||||
let x = 1;
|
||||
|
||||
function func() {
|
||||
console.log(x); // ?
|
||||
|
||||
let x = 2;
|
||||
}
|
||||
|
||||
func();
|
||||
```
|
||||
|
||||
P.S. There's a pitfall in this task. The solution is not obvious.
|
|
@ -1,203 +1,98 @@
|
|||
|
||||
# Closure
|
||||
# Variable scope
|
||||
|
||||
JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, copied to another variable or passed as an argument to another function and called from a totally different place later.
|
||||
JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, passed as an argument to another function and called from a totally different place of code later.
|
||||
|
||||
We know that a function can access variables outside of it, this feature is used quite often.
|
||||
We already know that a function can access variables outside of it.
|
||||
|
||||
But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created?
|
||||
Now let's expand our knowledge to include more complex scenarios.
|
||||
|
||||
Also, what happens when a function travels to another place in the code and is called from there -- does it get access to the outer variables of the new place?
|
||||
```smart header="We'll talk about `let/const` variables here"
|
||||
In JavaScript, there are 3 ways to declare a variable: `let`, `const` (the modern ones), and `var` (the remnant of the past).
|
||||
|
||||
Different languages behave differently here, and in this chapter we cover the behaviour of JavaScript.
|
||||
- In this article we'll use `let` variables in examples.
|
||||
- Variables, declared with `const`, behave the same, so this article is about `const` too.
|
||||
- The old `var` has some notable differences, they will be covered in the article <info:var>.
|
||||
```
|
||||
|
||||
## A couple of questions
|
||||
## Code blocks
|
||||
|
||||
Let's consider two situations to begin with, and then study the internal mechanics piece-by-piece, so that you'll be able to answer the following questions and more complex ones in the future.
|
||||
If a variable is declared inside a code block `{...}`, it's only visible inside that block.
|
||||
|
||||
1. The function `sayHi` uses an external variable `name`. When the function runs, which value is it going to use?
|
||||
|
||||
```js
|
||||
let name = "John";
|
||||
|
||||
function sayHi() {
|
||||
alert("Hi, " + name);
|
||||
}
|
||||
|
||||
name = "Pete";
|
||||
|
||||
*!*
|
||||
sayHi(); // what will it show: "John" or "Pete"?
|
||||
*/!*
|
||||
```
|
||||
|
||||
Such situations are common both in 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 the latest changes?
|
||||
|
||||
|
||||
2. The function `makeWorker` makes another function and returns it. That new function can be called from somewhere else. Will it have access to the outer variables from its creation place, or the invocation place, or both?
|
||||
|
||||
```js
|
||||
function makeWorker() {
|
||||
let name = "Pete";
|
||||
|
||||
return function() {
|
||||
alert(name);
|
||||
};
|
||||
}
|
||||
|
||||
let name = "John";
|
||||
|
||||
// create a function
|
||||
let work = makeWorker();
|
||||
|
||||
// call it
|
||||
*!*
|
||||
work(); // what will it show? "Pete" (name where created) or "John" (name where called)?
|
||||
*/!*
|
||||
```
|
||||
|
||||
|
||||
## Lexical Environment
|
||||
|
||||
To understand what's going on, let's first discuss what a "variable" actually is.
|
||||
|
||||
In JavaScript, every running function, code block `{...}`, and the script as a whole have an internal (hidden) associated object known as the *Lexical Environment*.
|
||||
|
||||
The Lexical Environment object consists of two parts:
|
||||
|
||||
1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`).
|
||||
2. A reference to the *outer lexical environment*, the one associated with the outer code.
|
||||
|
||||
**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:
|
||||
|
||||

|
||||
|
||||
This is a so-called global Lexical Environment, associated with the whole script.
|
||||
|
||||
On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, so it points to `null`.
|
||||
|
||||
And that's how it changes when a variable is defined and assigned:
|
||||
|
||||

|
||||
|
||||
Rectangles on the right-hand 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. It has been assigned no value, so `undefined` is stored.
|
||||
3. `phrase` is assigned a value.
|
||||
4. `phrase` changes value.
|
||||
|
||||
Everything looks simple for now, right?
|
||||
|
||||
To summarize:
|
||||
|
||||
- A variable is a property of a special internal object, associated with the currently executing block/function/script.
|
||||
- Working with variables is actually working with the properties of that object.
|
||||
|
||||
### Function Declaration
|
||||
|
||||
Until 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.
|
||||
|
||||
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`:
|
||||
|
||||

|
||||
|
||||
|
||||
### Inner and outer Lexical Environment
|
||||
|
||||
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.
|
||||
|
||||
When a function runs, a new Lexical Environment is created automatically 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";
|
||||
|
||||
function say(name) {
|
||||
alert( `${phrase}, ${name}` );
|
||||
}
|
||||
|
||||
say("John"); // Hello, John
|
||||
```-->
|
||||
|
||||

|
||||
|
||||
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 property: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`.
|
||||
- The outer Lexical Environment is the global Lexical Environment.
|
||||
|
||||
It has `phrase` variable and the function itself.
|
||||
|
||||
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 global one.**
|
||||
|
||||
If a variable is not found anywhere, that's an error in strict mode. Without `use strict`, an assignment to a non-existing variable like `user = "John"` creates a new global variable `user`. That's 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 reference to the enclosing Lexical Environment and finds it there.
|
||||
|
||||

|
||||
|
||||
Now we can give the answer to the first question from the beginning of the chapter.
|
||||
|
||||
**A function gets outer variables as they are now, it uses the most recent values.**
|
||||
|
||||
Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one.
|
||||
|
||||
So the answer to the first question is `Pete`:
|
||||
For example:
|
||||
|
||||
```js run
|
||||
let name = "John";
|
||||
{
|
||||
// do some job with local variables that should not be seen outside
|
||||
|
||||
function sayHi() {
|
||||
alert("Hi, " + name);
|
||||
let message = "Hello"; // only visible in this block
|
||||
|
||||
alert(message); // Hello
|
||||
}
|
||||
|
||||
name = "Pete"; // (*)
|
||||
alert(message); // Error: message is not defined
|
||||
```
|
||||
|
||||
We can use this to isolate a piece of code that does its own task, with variables that only belong to it:
|
||||
|
||||
```js run
|
||||
{
|
||||
// show message
|
||||
let message = "Hello";
|
||||
alert(message);
|
||||
}
|
||||
|
||||
{
|
||||
// show another message
|
||||
let message = "Goodbye";
|
||||
alert(message);
|
||||
}
|
||||
```
|
||||
|
||||
````smart header="There'd be an error without blocks"
|
||||
Please note, without separate blocks there would be an error, if we use `let` with the existing variable name:
|
||||
|
||||
```js run
|
||||
// show message
|
||||
let message = "Hello";
|
||||
alert(message);
|
||||
|
||||
// show another message
|
||||
*!*
|
||||
sayHi(); // Pete
|
||||
let message = "Goodbye"; // Error: variable already declared
|
||||
*/!*
|
||||
alert(message);
|
||||
```
|
||||
````
|
||||
|
||||
For `if`, `for`, `while` and so on, variables declared in `{...}` are also only visible inside:
|
||||
|
||||
```js run
|
||||
if (true) {
|
||||
let phrase = "Hello!";
|
||||
|
||||
alert(phrase); // Hello!
|
||||
}
|
||||
|
||||
alert(phrase); // Error, no such variable!
|
||||
```
|
||||
|
||||
Here, after `if` finishes, the `alert` below won't see the `phrase`, hence the error.
|
||||
|
||||
The execution flow of the code above:
|
||||
That's great, as it allows us to create block-local variables, specific to an `if` branch.
|
||||
|
||||
1. The global Lexical Environment has `name: "John"`.
|
||||
2. At the line `(*)` the global variable is changed. Now it has `name: "Pete"`.
|
||||
3. When the function `sayHi()` is executed it takes `name` from outside, the global Lexical Environment, where its value is already `"Pete"`.
|
||||
The similar thing holds true for `for` and `while` loops:
|
||||
|
||||
```js run
|
||||
for (let i = 0; i < 3; i++) {
|
||||
// the variable i is only visible inside this for
|
||||
alert(i); // 0, then 1, then 2
|
||||
}
|
||||
|
||||
```smart header="One call -- one Lexical Environment"
|
||||
Please note that a new function Lexical Environment is created each time a function runs.
|
||||
|
||||
And if a function is called multiple times, then each invocation will have its own Lexical Environment, with local variables and parameters specific for that very run.
|
||||
```
|
||||
|
||||
```smart header="Lexical Environment is a specification object"
|
||||
"Lexical Environment" is a specification object: it only exists "theoretically" in the [language specification](https://tc39.es/ecma262/#sec-lexical-environments) to describe how things work. 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, as long as the visible behavior remains as described.
|
||||
alert(i); // Error, no such variable
|
||||
```
|
||||
|
||||
Visually, `let i` is outside of `{...}`. But the `for` construct is special here: the variable, declared inside it, is considered a part of the block.
|
||||
|
||||
## Nested functions
|
||||
|
||||
|
@ -223,32 +118,16 @@ 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. Nested functions are quite common in JavaScript.
|
||||
|
||||
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.
|
||||
What's much more interesting, a nested function can be returned: either as a property of a new object 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.
|
||||
|
||||
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
|
||||
function User(name) {
|
||||
|
||||
// the object method is created as a nested function
|
||||
this.sayHi = function() {
|
||||
alert(name);
|
||||
};
|
||||
}
|
||||
|
||||
let user = new User("John");
|
||||
user.sayHi(); // the method "sayHi" code has access to the outer "name"
|
||||
```
|
||||
|
||||
And here we just create and return a "counting" function:
|
||||
Below, `makeCounter` creates the "counter" function that returns the next number on each invocation:
|
||||
|
||||
```js run
|
||||
function makeCounter() {
|
||||
let count = 0;
|
||||
|
||||
return function() {
|
||||
return count++; // has access to the outer "count"
|
||||
return count++;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -259,314 +138,195 @@ 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.
|
||||
Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [random number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) to generate random values for automated tests.
|
||||
|
||||
How does the counter work internally?
|
||||
How does this work? If we create multiple counters, will they be independent? What's going on with the variables here?
|
||||
|
||||
When the inner function runs, the variable in `count++` is searched from inside out. For the example above, the order will be:
|
||||
Undestanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth.
|
||||
|
||||

|
||||
## Lexical Environment
|
||||
|
||||
1. The locals of the nested function...
|
||||
2. The variables of the outer function...
|
||||
3. And so on until it reaches global variables.
|
||||
```warn header="Here be dragons!"
|
||||
The in-depth technical explanation lies ahead.
|
||||
|
||||
In this example `count` is found on 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`.
|
||||
As far as I'd like to avoid low-level language details, any understanding without them would be lacking and incomplete, so get ready.
|
||||
```
|
||||
|
||||
Here are two questions to consider:
|
||||
For clarity, the explanation is split into multiple steps.
|
||||
|
||||
1. Can we somehow reset the counter `count` 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`?
|
||||
### Step 1. Variables
|
||||
|
||||
Try to answer them before you continue reading.
|
||||
In JavaScript, every running function, code block `{...}`, and the script as a whole have an internal (hidden) associated object known as the *Lexical Environment*.
|
||||
|
||||
...
|
||||
The Lexical Environment object consists of two parts:
|
||||
|
||||
All done?
|
||||
1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`).
|
||||
2. A reference to the *outer lexical environment*, the one associated with the outer code.
|
||||
|
||||
Okay, let's go over the answers.
|
||||
**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".**
|
||||
|
||||
1. There is no way: `count` 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 `count`. So the resulting `counter` functions are independent.
|
||||
In this simple code without functions, there is only one Lexical Environment:
|
||||
|
||||
Here's the demo:
|
||||

|
||||
|
||||
```js run
|
||||
This is the so-called *global* Lexical Environment, associated with the whole script.
|
||||
|
||||
On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, that's why the arrow points to `null`.
|
||||
|
||||
As the code starts executing and goes on, the Lexical Environment changes.
|
||||
|
||||
Here's a little bit longer code:
|
||||
|
||||

|
||||
|
||||
Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution:
|
||||
|
||||
1. When the script starts, the Lexical Environment is pre-populated with all declared variables.
|
||||
- Initially, they are in the "Uninitialized" state. That's a special internal state, it means that the engine knows about the variable, but won't allow to use it before `let`. It's almost the same as if the variable didn't exist.
|
||||
2. Then `let phrase` definition appears. There's no assignment yet, so its value is `undefined`. We can use the variable since this moment.
|
||||
3. `phrase` is assigned a value.
|
||||
4. `phrase` changes the value.
|
||||
|
||||
Everything looks simple for now, right?
|
||||
|
||||
- A variable is a property of a special internal object, associated with the currently executing block/function/script.
|
||||
- Working with variables is actually working with the properties of that object.
|
||||
|
||||
```smart header="Lexical Environment is a specification object"
|
||||
"Lexical Environment" is a specification object: it only exists "theoretically" in the [language specification](https://tc39.es/ecma262/#sec-lexical-environments) to describe how things work. 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, as long as the visible behavior remains as described.
|
||||
```
|
||||
|
||||
### Step 2. Function Declarations
|
||||
|
||||
A function is also a value, like a variable.
|
||||
|
||||
**The difference is that a Function Declaration is instantly fully initialized.**
|
||||
|
||||
When a Lexical Environment is created, a Function Declaration immediately becomes a ready-to-use function (unlike `let`, that is unusable till the declaration).
|
||||
|
||||
That's why we can use a function, declared as Function Declaration, even before the declaration itself.
|
||||
|
||||
For example, here's the initial state of the global Lexical Environment when we add a function:
|
||||
|
||||

|
||||
|
||||
Naturally, this behavior only applies to Function Declarations, not Function Expressions where we assign a function to a variable, such as `let say = function(name)...`.
|
||||
|
||||
### Step 3. Inner and outer Lexical Environment
|
||||
|
||||
When a function runs, at the beginning of the call, a new Lexical Environment is created automatically 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";
|
||||
|
||||
function say(name) {
|
||||
alert( `${phrase}, ${name}` );
|
||||
}
|
||||
|
||||
say("John"); // Hello, John
|
||||
```-->
|
||||
|
||||

|
||||
|
||||
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 property: `name`, the function argument. We called `say("John")`, so the value of the `name` is `"John"`.
|
||||
- The outer Lexical Environment is the global Lexical Environment. It has the `phrase` variable and the function itself.
|
||||
|
||||
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 global one.**
|
||||
|
||||
If a variable is not found anywhere, that's an error in strict mode (without `use strict`, an assignment to a non-existing variable creates a new global variable, for compatibility with old code).
|
||||
|
||||
In this example the search proceeds as follows:
|
||||
|
||||
- For the `name` variable, the `alert` inside `say` finds it immediately in the inner Lexical Environment.
|
||||
- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the reference to the outer Lexical Environment and finds it there.
|
||||
|
||||

|
||||
|
||||
|
||||
### Step 4. Returning a function
|
||||
|
||||
Let's return to the `makeCounter` example.
|
||||
|
||||
```js
|
||||
function makeCounter() {
|
||||
let count = 0;
|
||||
|
||||
return function() {
|
||||
return count++;
|
||||
};
|
||||
}
|
||||
|
||||
let counter1 = makeCounter();
|
||||
let counter2 = makeCounter();
|
||||
|
||||
alert( counter1() ); // 0
|
||||
alert( counter1() ); // 1
|
||||
|
||||
alert( counter2() ); // 0 (independent)
|
||||
let counter = makeCounter();
|
||||
```
|
||||
|
||||
At the beginning of each `makeCounter()` call, a new Lexical Environment object is created, to store variables for this `makeCounter` run.
|
||||
|
||||
Hopefully, the situation with outer variables is clear now. For most situations such understanding is enough. There are few details in the specification that we omitted for brevity. So in the next section we cover even more details.
|
||||
So we have two nested Lexical Environments, just like in the example above:
|
||||
|
||||
## Environments in detail
|
||||

|
||||
|
||||
Here's what's going on in the `makeCounter` example step-by-step. Follow it to make sure that you understand how it works in detail.
|
||||
What's different is that, during the execution of `makeCounter()`, a tiny nested function is created of only one line: `return count++`. We don't run it yet, only create.
|
||||
|
||||
Please note the additional `[[Environment]]` property is covered here. We didn't mention it before for simplicity.
|
||||
All functions remember the Lexical Environment in which they were made. Technically, there's no magic here: all functions have the hidden property named `[[Environment]]`, that keeps the reference to the Lexical Environment where the function was created:
|
||||
|
||||
1. When the script has just started, there is only the global Lexical Environment:
|
||||

|
||||
|
||||

|
||||
So, `counter.[[Environment]]` has the reference to `{count: 0}` Lexical Environment. That's how the function remembers where it was created, no matter where it's called. The `[[Environment]]` reference is set once and forever at function creation time.
|
||||
|
||||
At that starting moment there is only the `makeCounter` function, because it's a Function Declaration. It did not run yet.
|
||||
Later, when `counter()` is called, a new Lexical Environment is created for the call, and its outer Lexical Environment reference is taken from `counter.[[Environment]]`:
|
||||
|
||||
**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 before. That's how the function knows where it was made.
|
||||
Now when the code inside `counter()` looks for `count` variable, it first searches its own Lexical Environment (empty, as there are no local variables there), then the Lexical Environment of the outer `makeCounter()` call, where finds it and changes.
|
||||
|
||||
Here, `makeCounter` is created in the global Lexical Environment, so `[[Environment]]` keeps a reference to it.
|
||||
**A variable is updated in the Lexical Environment where it lives.**
|
||||
|
||||
In other words, a function is "imprinted" with a reference to the Lexical Environment where it was born. And `[[Environment]]` is the hidden function property that has that reference.
|
||||
Here's the state after the execution:
|
||||
|
||||
2. The code runs on, the new global variable `counter` is declared and gets the result of the `makeCounter()` call. Here's a snapshot of the moment when the execution is on the first line inside `makeCounter()`:
|
||||

|
||||
|
||||

|
||||
If we call `counter()` multiple times, the `count` variable will be increased to `2`, `3` and so on, at the same place.
|
||||
|
||||
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:
|
||||
1. An Environment Record with local variables. In our case `count` is the only local variable (appearing when the line with `let count` is executed).
|
||||
2. The outer lexical reference, which is set to the value of `[[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 whether the function is created using Function Declaration or Function Expression. All functions get the `[[Environment]]` property that references the Lexical Environment in which they were made. So our new tiny nested function gets it as well.
|
||||
|
||||
For our new nested function the value of `[[Environment]]` is the current Lexical Environment of `makeCounter()` (where it was born):
|
||||
|
||||

|
||||
|
||||
Please note that on this step the inner function was created, but not yet called. The code inside `return count++;` is not running.
|
||||
|
||||
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`:
|
||||
|
||||

|
||||
|
||||
That function has only one line: `return count++`, that will be executed when we run it.
|
||||
|
||||
5. When `counter()` is called, a new Lexical Environment is created for the call. It's empty, as `counter` has no local variables by itself. But the `[[Environment]]` of `counter` is used as the `outer` reference for it, that provides access to the variables of the former `makeCounter()` call where it was created:
|
||||
|
||||

|
||||
|
||||
Now when the call looks for `count` variable, it first searches its own Lexical Environment (empty), then the Lexical Environment of the outer `makeCounter()` call, where it finds it.
|
||||
|
||||
Please note how memory management works here. Although `makeCounter()` call finished some time ago, its Lexical Environment was retained in memory, because there's a nested function with `[[Environment]]` referencing it.
|
||||
|
||||
Generally, a Lexical Environment object lives as long as there is a function which may use it. And only when there are none remaining, it is cleared.
|
||||
|
||||
6. The call to `counter()` not only returns the value of `count`, but also increases it. Note that the modification is done "in place". The value of `count` is modified exactly in the environment where it was found.
|
||||
|
||||

|
||||
|
||||
7. Next `counter()` invocations do the same.
|
||||
|
||||
The answer to the second question from the beginning of the chapter should now be obvious.
|
||||
|
||||
The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference:
|
||||
|
||||

|
||||
|
||||
So, the result is `"Pete"` here.
|
||||
|
||||
But if there were no `let name` in `makeWorker()`, then the search would go outside and take the global variable as we can see from the chain above. In that case it would be `"John"`.
|
||||
|
||||
```smart header="Closures"
|
||||
```smart header="Closure"
|
||||
There is a general programming term "closure", that developers generally should know.
|
||||
|
||||
A [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exclusion, to be covered in <info:new-function>).
|
||||
A [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exception, to be covered in <info:new-function>).
|
||||
|
||||
That is: they automatically remember where they were created using a hidden `[[Environment]]` property, and all of them can access outer variables.
|
||||
That is: they automatically remember where they were created using a hidden `[[Environment]]` property, and then their code can access outer variables.
|
||||
|
||||
When on an interview, a frontend developer gets a question about "what's a closure?", a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the `[[Environment]]` property and how Lexical Environments work.
|
||||
```
|
||||
|
||||
## Code blocks and loops, IIFE
|
||||
|
||||
The examples above concentrated on functions. But a Lexical Environment exists for any code block `{...}`.
|
||||
|
||||
A Lexical Environment is created when a code block runs and contains block-local variables. Here are a couple of examples.
|
||||
|
||||
### If
|
||||
|
||||
In the example below, the `user` variable exists only in the `if` block:
|
||||
|
||||
<!--
|
||||
```js run
|
||||
let phrase = "Hello";
|
||||
|
||||
if (true) {
|
||||
let user = "John";
|
||||
|
||||
alert(`${phrase}, ${user}`); // Hello, John
|
||||
}
|
||||
|
||||
alert(user); // Error, can't see such variable!
|
||||
```-->
|
||||
|
||||

|
||||
|
||||
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 a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for(let ...)`, then it's also in there:
|
||||
|
||||
```js run
|
||||
for (let i = 0; i < 10; i++) {
|
||||
// Each loop has its own Lexical Environment
|
||||
// {i: value}
|
||||
}
|
||||
|
||||
alert(i); // Error, no such variable
|
||||
```
|
||||
|
||||
Please note: `let i` is visually outside of `{...}`. The `for` construct is special here: each iteration of the loop has its own Lexical Environment with the current `i` in it.
|
||||
|
||||
Again, similarly to `if`, after the loop `i` is not visible.
|
||||
|
||||
### 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 (except with `type="module"`) share the same global area. So if we create a global variable in one script, it becomes available to others. But that becomes 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 avoid that, we can use a code block to isolate the whole script or a part of it:
|
||||
|
||||
```js run
|
||||
{
|
||||
// do some job with local variables that should not be seen outside
|
||||
|
||||
let message = "Hello";
|
||||
|
||||
alert(message); // Hello
|
||||
}
|
||||
|
||||
alert(message); // Error: message is not defined
|
||||
```
|
||||
|
||||
The code outside of the block (or inside another script) doesn't see variables inside the block, because the block has its own Lexical Environment.
|
||||
|
||||
### IIFE
|
||||
|
||||
In the past, there were no block-level lexical environments in JavaScript.
|
||||
|
||||
So programmers had to invent something. And what they did was 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.
|
||||
|
||||
An IIFE looks like this:
|
||||
|
||||
```js run
|
||||
(function() {
|
||||
|
||||
let message = "Hello";
|
||||
|
||||
alert(message); // Hello
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
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 this kind of code will give an error:
|
||||
|
||||
```js run
|
||||
// Try to declare and immediately call a function
|
||||
function() { // <-- Error: Unexpected token (
|
||||
|
||||
let message = "Hello";
|
||||
|
||||
alert(message); // Hello
|
||||
|
||||
}();
|
||||
```
|
||||
|
||||
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
|
||||
function go() {
|
||||
|
||||
}(); // <-- can't call Function Declaration immediately
|
||||
```
|
||||
|
||||
So, the 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 exist other ways besides parentheses to tell JavaScript that we mean a Function Expression:
|
||||
|
||||
```js run
|
||||
// Ways to create IIFE
|
||||
|
||||
(function() {
|
||||
alert("Parentheses around the function");
|
||||
}*!*)*/!*();
|
||||
|
||||
(function() {
|
||||
alert("Parentheses around the whole thing");
|
||||
}()*!*)*/!*;
|
||||
|
||||
*!*!*/!*function() {
|
||||
alert("Bitwise NOT operator starts the expression");
|
||||
}();
|
||||
|
||||
*!*+*/!*function() {
|
||||
alert("Unary plus starts the expression");
|
||||
}();
|
||||
```
|
||||
|
||||
In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code.
|
||||
|
||||
## Garbage collection
|
||||
|
||||
Usually, a Lexical Environment is cleaned up and deleted after the function runs. For instance:
|
||||
Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That's because there are no references to it. As any JavaScript object, it's only kept in memory while it's reachable.
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value1 = 123;
|
||||
let value2 = 456;
|
||||
}
|
||||
...But if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment.
|
||||
|
||||
f();
|
||||
```
|
||||
In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive.
|
||||
|
||||
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 it has `[[Environment]]` property that references the outer lexical environment, so it's also reachable and alive:
|
||||
For example:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value = 123;
|
||||
|
||||
function g() { alert(value); }
|
||||
|
||||
*!*
|
||||
return g;
|
||||
*/!*
|
||||
return function() {
|
||||
alert(value);
|
||||
}
|
||||
}
|
||||
|
||||
let func = f(); // func gets a reference to g
|
||||
// so it stays and memory and its outer lexical environment stays as well
|
||||
let g = f(); // g.[[Environment]] stores a reference to the Lexical Environment
|
||||
// of the corresponding f() call
|
||||
```
|
||||
|
||||
Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
|
||||
|
@ -585,20 +345,20 @@ 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.
|
||||
|
||||
In the code below, after `g` becomes unreachable, its enclosing Lexical Environment (and hence the `value`) is cleaned from memory;
|
||||
In the code below, after the nested function is removed, its enclosing Lexical Environment (and hence the `value`) is cleaned from memory;
|
||||
|
||||
```js
|
||||
function f() {
|
||||
let value = 123;
|
||||
|
||||
function g() { alert(value); }
|
||||
|
||||
return g;
|
||||
return function() {
|
||||
alert(value);
|
||||
}
|
||||
}
|
||||
|
||||
let func = f(); // while func has a reference to g, it stays in memory
|
||||
let g = f(); // while g function exists, the value stays in memory
|
||||
|
||||
func = null; // ...and now the memory is cleaned up
|
||||
g = null; // ...and now the memory is cleaned up
|
||||
```
|
||||
|
||||
### Real-life optimizations
|
||||
|
@ -649,9 +409,6 @@ let g = f();
|
|||
g();
|
||||
```
|
||||
|
||||
```warn header="See ya!"
|
||||
This feature of V8 is good to know. If you are debugging with Chrome/Opera, sooner or later you will meet it.
|
||||
|
||||
That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime.
|
||||
You always can check for it by running the examples on this page.
|
||||
```
|
||||
That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You always can check for it by running the examples on this page.
|
||||
|
|
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 7.7 KiB |
|
@ -1,6 +1,12 @@
|
|||
|
||||
# The old "var"
|
||||
|
||||
```smart header="This article is for understanding old scripts"
|
||||
The information in this article is useful for understanding old scripts.
|
||||
|
||||
That's not how we write a new code.
|
||||
```
|
||||
|
||||
In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration:
|
||||
|
||||
1. `let`
|
||||
|
@ -188,6 +194,74 @@ Because all `var` declarations are processed at the function start, we can refer
|
|||
|
||||
In both examples above `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`.
|
||||
|
||||
### IIFE
|
||||
|
||||
As in the past there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE).
|
||||
|
||||
That's not something we should use nowadays, but you can find them in old scripts.
|
||||
|
||||
An IIFE looks like this:
|
||||
|
||||
```js run
|
||||
(function() {
|
||||
|
||||
let message = "Hello";
|
||||
|
||||
alert(message); // Hello
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
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 this kind of code will give an error:
|
||||
|
||||
```js run
|
||||
// Try to declare and immediately call a function
|
||||
function() { // <-- Error: Unexpected token (
|
||||
|
||||
let message = "Hello";
|
||||
|
||||
alert(message); // Hello
|
||||
|
||||
}();
|
||||
```
|
||||
|
||||
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
|
||||
function go() {
|
||||
|
||||
}(); // <-- can't call Function Declaration immediately
|
||||
```
|
||||
|
||||
So, the 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 exist other ways besides parentheses to tell JavaScript that we mean a Function Expression:
|
||||
|
||||
```js run
|
||||
// Ways to create IIFE
|
||||
|
||||
(function() {
|
||||
alert("Parentheses around the function");
|
||||
}*!*)*/!*();
|
||||
|
||||
(function() {
|
||||
alert("Parentheses around the whole thing");
|
||||
}()*!*)*/!*;
|
||||
|
||||
*!*!*/!*function() {
|
||||
alert("Bitwise NOT operator starts the expression");
|
||||
}();
|
||||
|
||||
*!*+*/!*function() {
|
||||
alert("Unary plus starts the expression");
|
||||
}();
|
||||
```
|
||||
|
||||
In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code.
|
||||
|
||||
## Summary
|
||||
|
||||
There are two main differences of `var` compared to `let/const`:
|
||||
|
@ -195,6 +269,6 @@ There are two main differences of `var` compared to `let/const`:
|
|||
1. `var` variables have no block scope, they are visible minimum at the function level.
|
||||
2. `var` declarations are processed at function start (script start for globals).
|
||||
|
||||
There's one more minor difference related to the global object, we'll cover that in the next chapter.
|
||||
There's one more very minor difference related to the global object, that we'll cover in the next chapter.
|
||||
|
||||
These differences make `var` worse than `let` most of the time. Block-level variables is such a great thing. That's why `let` was introduced in the standard long ago, and is now a major way (along with `const`) to declare a variable.
|
||||
|
|
|
@ -299,13 +299,13 @@ The only syntax difference between `call` and `apply` is that `call` expects a l
|
|||
So these two calls are almost equivalent:
|
||||
|
||||
```js
|
||||
func.call(context, ...args); // pass an array as list with spread operator
|
||||
func.call(context, ...args); // pass an array as list with spread syntax
|
||||
func.apply(context, args); // is same as using apply
|
||||
```
|
||||
|
||||
There's only a minor difference:
|
||||
|
||||
- The spread operator `...` allows to pass *iterable* `args` as the list to `call`.
|
||||
- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`.
|
||||
- The `apply` accepts only *array-like* `args`.
|
||||
|
||||
So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works.
|
||||
|
|
|
@ -313,7 +313,7 @@ The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that call
|
|||
- Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`)
|
||||
- Then gives it `...args` -- arguments given to the wrapper (`"Hello"`)
|
||||
|
||||
So easy to do it with the spread operator, right?
|
||||
So easy to do it with the spread syntax, right?
|
||||
|
||||
Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library.
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ alert(user.fullName); // John Smith
|
|||
for(let key in user) alert(key); // name, surname
|
||||
```
|
||||
|
||||
Please note once again that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both.
|
||||
Please note that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both.
|
||||
|
||||
If we try to supply both `get` and `value` in the same descriptor, there will be an error:
|
||||
|
||||
|
|
|
@ -499,7 +499,7 @@ In the example below a non-method syntax is used for comparison. `[[HomeObject]]
|
|||
|
||||
```js run
|
||||
let animal = {
|
||||
eat: function() { // intentially writing like this instead of eat() {...
|
||||
eat: function() { // intentionally writing like this instead of eat() {...
|
||||
// ...
|
||||
}
|
||||
};
|
||||
|
|
|
@ -279,7 +279,7 @@ With private fields that's impossible: `this['#name']` doesn't work. That's a sy
|
|||
|
||||
## Summary
|
||||
|
||||
In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)").
|
||||
In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)).
|
||||
|
||||
It gives the following benefits:
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', s
|
|||
|
||||
That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a `callback` argument where we put the function to run after it's complete.
|
||||
|
||||
Here we did it in `loadScript`, but of course, it's a general approach.
|
||||
Here we did it in `loadScript`, but of course it's a general approach.
|
||||
|
||||
## Callback in callback
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ Everyone is happy: you, because the people don't crowd you anymore, and fans, be
|
|||
|
||||
This is a real-life analogy for things we often have in programming:
|
||||
|
||||
1. A "producing code" that does something and takes time. For instance, a code that loads the data over a network. That's a "singer".
|
||||
1. A "producing code" that does something and takes time. For instance, some code that loads the data over a network. That's a "singer".
|
||||
2. A "consuming code" that wants the result of the "producing code" once it's ready. Many functions may need that result. These are the "fans".
|
||||
3. A *promise* is a special JavaScript object that links the "producing code" and the "consuming code" together. In terms of our analogy: this is the "subscription list". The "producing code" takes whatever time it needs to produce the promised result, and the "promise" makes that result available to all of the subscribed code when it's ready.
|
||||
|
||||
|
@ -22,7 +22,7 @@ let promise = new Promise(function(resolve, reject) {
|
|||
});
|
||||
```
|
||||
|
||||
The function passed to `new Promise` is called the *executor*. When `new Promise` is created, it runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above, the executor is the "singer".
|
||||
The function passed to `new Promise` is called the *executor*. When `new Promise` is created, the executor runs automatically. It contains the producing code which should eventually produce the result. In terms of the analogy above: the executor is the "singer".
|
||||
|
||||
Its arguments `resolve` and `reject` are callbacks provided by JavaScript itself. Our code is only inside the executor.
|
||||
|
||||
|
@ -31,7 +31,7 @@ When the executor obtains the result, be it soon or late - doesn't matter, it sh
|
|||
- `resolve(value)` — if the job finished successfully, with result `value`.
|
||||
- `reject(error)` — if an error occurred, `error` is the error object.
|
||||
|
||||
So to summarize: the executor runs automatically and attempts to perform a job. When it is finished with the attempt it calls `resolve` if it was succssful or `reject` if there was an error.
|
||||
So to summarize: the executor runs automatically and performs a job. Then it should call `resolve` if it was succssful or `reject` if there was an error.
|
||||
|
||||
The `promise` object returned by the `new Promise` constructor has internal properties:
|
||||
|
||||
|
@ -272,6 +272,10 @@ let promise = new Promise(resolve => resolve("done!"));
|
|||
|
||||
promise.then(alert); // done! (shows up right now)
|
||||
```
|
||||
|
||||
Note that this is different, and more powerful than the real life "subscription list" scenario. If the singer has already released their song and then a person signs up on the subscription list, they probably won't receive that song. Subscriptions in real life must be done prior to the event.
|
||||
|
||||
Promises are more flexible. We can add handlers any time: if the result is already there, our handlers get it immediately.
|
||||
````
|
||||
|
||||
Next, let's see more practical examples of how promises can help us write asynchronous code.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
# Promises chaining
|
||||
|
||||
Let's return to the problem mentioned in the chapter <info:callbacks>: we have a sequence of asynchronous tasks to be done one after another. For instance, loading scripts. How can we code it well?
|
||||
Let's return to the problem mentioned in the chapter <info:callbacks>: we have a sequence of asynchronous tasks to be performed one after another — for instance, loading scripts. How can we code it well?
|
||||
|
||||
Promises provide a couple of recipes to do that.
|
||||
|
||||
|
@ -72,7 +72,7 @@ promise.then(function(result) {
|
|||
});
|
||||
```
|
||||
|
||||
What we did here is just several handlers to one promise. They don't pass the result to each other, instead they process it independently.
|
||||
What we did here is just several handlers to one promise. They don't pass the result to each other; instead they process it independently.
|
||||
|
||||
Here's the picture (compare it with the chaining above):
|
||||
|
||||
|
@ -164,7 +164,7 @@ loadScript("/article/promise-chaining/one.js")
|
|||
|
||||
Here each `loadScript` call returns a promise, and the next `.then` runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.
|
||||
|
||||
We can add more asynchronous actions to the chain. Please note that the code is still "flat", it grows down, not to the right. There are no signs of "pyramid of doom".
|
||||
We can add more asynchronous actions to the chain. Please note that the code is still "flat" — it grows down, not to the right. There are no signs of the "pyramid of doom".
|
||||
|
||||
Technically, we could add `.then` directly to each `loadScript`, like this:
|
||||
|
||||
|
@ -287,7 +287,7 @@ fetch('/article/promise-chaining/user.json')
|
|||
});
|
||||
```
|
||||
|
||||
The code works, see comments about the details. However, there's a potential problem in it, a typical error of those who begin to use promises.
|
||||
The code works; see comments about the details. However, there's a potential problem in it, a typical error for those who begin to use promises.
|
||||
|
||||
Look at the line `(*)`: how can we do something *after* the avatar has finished showing and gets removed? For instance, we'd like to show a form for editing that user or something else. As of now, there's no way.
|
||||
|
||||
|
@ -319,13 +319,9 @@ fetch('/article/promise-chaining/user.json')
|
|||
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
|
||||
```
|
||||
|
||||
That is, `.then` handler in line `(*)` now returns `new Promise`, that becomes settled only after the call of `resolve(githubUser)` in `setTimeout` `(**)`.
|
||||
That is, the `.then` handler in line `(*)` now returns `new Promise`, that becomes settled only after the call of `resolve(githubUser)` in `setTimeout` `(**)`. The next `.then` in the chain will wait for that.
|
||||
|
||||
The next `.then` in chain will wait for that.
|
||||
|
||||
As a good practice, an asynchronous action should always return a promise.
|
||||
|
||||
That makes it possible to plan actions after it. Even if we don't plan to extend the chain now, we may need it later.
|
||||
As a good practice, an asynchronous action should always return a promise. That makes it possible to plan actions after it; even if we don't plan to extend the chain now, we may need it later.
|
||||
|
||||
Finally, we can split the code into reusable functions:
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ The final `.catch` not only catches explicit rejections, but also occasional err
|
|||
|
||||
As we already noticed, `.catch` at the end of the chain is similar to `try..catch`. We may have as many `.then` handlers as we want, and then use a single `.catch` at the end to handle errors in all of them.
|
||||
|
||||
In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises.
|
||||
In a regular `try..catch` we can analyze the error and maybe rethrow it if it can't be handled. The same thing is possible for promises.
|
||||
|
||||
If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler.
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ There are 5 static methods in the `Promise` class. We'll quickly cover their use
|
|||
|
||||
## Promise.all
|
||||
|
||||
Let's say we want to run many promises to execute in parallel, and wait until all of them are ready.
|
||||
Let's say we want many promises to execute in parallel and wait until all of them are ready.
|
||||
|
||||
For instance, download several URLs in parallel and process the content when all are done.
|
||||
For instance, download several URLs in parallel and process the content once they are all done.
|
||||
|
||||
That's what `Promise.all` is for.
|
||||
|
||||
|
@ -18,7 +18,7 @@ let promise = Promise.all([...promises...]);
|
|||
|
||||
`Promise.all` takes an array of promises (it technically can be any iterable, but is usually an array) and returns a new promise.
|
||||
|
||||
The new promise resolves when all listed promises are settled and the array of their results becomes its result.
|
||||
The new promise resolves when all listed promises are settled, and the array of their results becomes its result.
|
||||
|
||||
For instance, the `Promise.all` below settles after 3 seconds, and then its result is an array `[1, 2, 3]`:
|
||||
|
||||
|
@ -89,12 +89,12 @@ Promise.all([
|
|||
]).catch(alert); // Error: Whoops!
|
||||
```
|
||||
|
||||
Here the second promise rejects in two seconds. That leads to an immediate rejection of `Promise.all`, so `.catch` executes: the rejection error becomes the outcome of the whole `Promise.all`.
|
||||
Here the second promise rejects in two seconds. That leads to an immediate rejection of `Promise.all`, so `.catch` executes: the rejection error becomes the outcome of the entire `Promise.all`.
|
||||
|
||||
```warn header="In case of an error, other promises are ignored"
|
||||
If one promise rejects, `Promise.all` immediately rejects, completely forgetting about the other ones in the list. Their results are ignored.
|
||||
|
||||
For example, if there are multiple `fetch` calls, like in the example above, and one fails, other ones will still continue to execute, but `Promise.all` won't watch them anymore. They will probably settle, but the result will be ignored.
|
||||
For example, if there are multiple `fetch` calls, like in the example above, and one fails, the others will still continue to execute, but `Promise.all` won't watch them anymore. They will probably settle, but their results will be ignored.
|
||||
|
||||
`Promise.all` does nothing to cancel them, as there's no concept of "cancellation" in promises. In [another chapter](info:fetch-abort) we'll cover `AbortController` that can help with that, but it's not a part of the Promise API.
|
||||
```
|
||||
|
@ -121,7 +121,7 @@ So we are able to pass ready values to `Promise.all` where convenient.
|
|||
|
||||
[recent browser="new"]
|
||||
|
||||
`Promise.all` rejects as a whole if any promise rejects. That's good for "all or nothing" cases, when we need *all* results to go on:
|
||||
`Promise.all` rejects as a whole if any promise rejects. That's good for "all or nothing" cases, when we need *all* results successful to proceed:
|
||||
|
||||
```js
|
||||
Promise.all([
|
||||
|
@ -131,7 +131,7 @@ Promise.all([
|
|||
]).then(render); // render method needs results of all fetches
|
||||
```
|
||||
|
||||
`Promise.allSettled` waits for all promises to settle. The resulting array has:
|
||||
`Promise.allSettled` just waits for all promises to settle, regardless of the result. The resulting array has:
|
||||
|
||||
- `{status:"fulfilled", value:result}` for successful responses,
|
||||
- `{status:"rejected", reason:error}` for errors.
|
||||
|
@ -169,7 +169,7 @@ The `results` in the line `(*)` above will be:
|
|||
]
|
||||
```
|
||||
|
||||
So, for each promise we get its status and `value/error`.
|
||||
So for each promise we get its status and `value/error`.
|
||||
|
||||
### Polyfill
|
||||
|
||||
|
@ -197,7 +197,7 @@ Now we can use `Promise.allSettled` to get the results of *all* given promises,
|
|||
|
||||
## Promise.race
|
||||
|
||||
Similar to `Promise.all`, but waits only for the first settled promise, and gets its result (or error).
|
||||
Similar to `Promise.all`, but waits only for the first settled promise and gets its result (or error).
|
||||
|
||||
The syntax is:
|
||||
|
||||
|
@ -222,9 +222,11 @@ The first promise here was fastest, so it became the result. After the first set
|
|||
|
||||
Methods `Promise.resolve` and `Promise.reject` are rarely needed in modern code, because `async/await` syntax (we'll cover it [a bit later](info:async-await)) makes them somewhat obsolete.
|
||||
|
||||
We cover them here for completeness, and for those who can't use `async/await` for some reason.
|
||||
We cover them here for completeness and for those who can't use `async/await` for some reason.
|
||||
|
||||
- `Promise.resolve(value)` creates a resolved promise with the result `value`.
|
||||
### Promise.resolve
|
||||
|
||||
`Promise.resolve(value)` creates a resolved promise with the result `value`.
|
||||
|
||||
Same as:
|
||||
|
||||
|
@ -234,7 +236,7 @@ let promise = new Promise(resolve => resolve(value));
|
|||
|
||||
The method is used for compatibility, when a function is expected to return a promise.
|
||||
|
||||
For example, `loadCached` function below fetches a URL and remembers (caches) its content. For future calls with the same URL it immediately gets the previous content from cache, but uses `Promise.resolve` to make a promise of it, so that the returned value is always a promise:
|
||||
For example, the `loadCached` function below fetches a URL and remembers (caches) its content. For future calls with the same URL it immediately gets the previous content from cache, but uses `Promise.resolve` to make a promise of it, so the returned value is always a promise:
|
||||
|
||||
```js
|
||||
let cache = new Map();
|
||||
|
@ -259,7 +261,7 @@ We can write `loadCached(url).then(…)`, because the function is guaranteed to
|
|||
|
||||
### Promise.reject
|
||||
|
||||
- `Promise.reject(error)` creates a rejected promise with `error`.
|
||||
`Promise.reject(error)` creates a rejected promise with `error`.
|
||||
|
||||
Same as:
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Promisification
|
||||
|
||||
Promisification -- is a long word for a simple transform. It's conversion of a function that accepts a callback into a function returning a promise.
|
||||
"Promisification" is a long word for a simple transformation. It's the conversion of a function that accepts a callback into a function that returns a promise.
|
||||
|
||||
Such transforms are often needed in real-life, as many functions and libraries are callback-based. But promises are more convenient. So it makes sense to promisify those.
|
||||
Such transformations are often required in real-life, as many functions and libraries are callback-based. But promises are more convenient, so it makes sense to promisify them.
|
||||
|
||||
For instance, we have `loadScript(src, callback)` from the chapter <info:callbacks>.
|
||||
|
||||
|
@ -21,7 +21,7 @@ function loadScript(src, callback) {
|
|||
// loadScript('path/script.js', (err, script) => {...})
|
||||
```
|
||||
|
||||
Let's promisify it. The new `loadScriptPromise(src)` function will do the same, but accept only `src` (no `callback`) and return a promise.
|
||||
Let's promisify it. The new `loadScriptPromise(src)` function achieves the same result, but it accepts only `src` (no `callback`) and returns a promise.
|
||||
|
||||
```js
|
||||
let loadScriptPromise = function(src) {
|
||||
|
@ -41,9 +41,7 @@ Now `loadScriptPromise` fits well in promise-based code.
|
|||
|
||||
As we can see, it delegates all the work to the original `loadScript`, providing its own callback that translates to promise `resolve/reject`.
|
||||
|
||||
In practice we'll probably need to promisify many functions, it makes sense to use a helper.
|
||||
|
||||
We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function.
|
||||
In practice we'll probably need to promisify many functions, so it makes sense to use a helper. We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function.
|
||||
|
||||
That wrapper does the same as in the code above: returns a promise and passes the call to the original `f`, tracking the result in a custom callback:
|
||||
|
||||
|
@ -103,7 +101,7 @@ f = promisify(f, true);
|
|||
f(...).then(arrayOfResults => ..., err => ...)
|
||||
```
|
||||
|
||||
For more exotic callback formats, like those without `err` at all: `callback(result)`, we can promisify such functions without using the helper, manually.
|
||||
For more exotic callback formats, like those without `err` at all: `callback(result)`, we can promisify such functions manually without using the helper.
|
||||
|
||||
There are also modules with a bit more flexible promisification functions, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). In Node.js, there's a built-in `util.promisify` function for that.
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
Promise handlers `.then`/`.catch`/`.finally` are always asynchronous.
|
||||
|
||||
Even when a Promise is immediately resolved, the code on the lines *below* `.then`/`.catch`/`.finally` will still execute before these handlers .
|
||||
Even when a Promise is immediately resolved, the code on the lines *below* `.then`/`.catch`/`.finally` will still execute before these handlers.
|
||||
|
||||
Here's the demo:
|
||||
Here's a demo:
|
||||
|
||||
```js run
|
||||
let promise = Promise.resolve();
|
||||
|
@ -23,14 +23,14 @@ Why did the `.then` trigger afterwards? What's going on?
|
|||
|
||||
## Microtasks queue
|
||||
|
||||
Asynchronous tasks need proper management. For that, the standard specifies an internal queue `PromiseJobs`, more often referred to as "microtask queue" (v8 term).
|
||||
Asynchronous tasks need proper management. For that, the Ecma standard specifies an internal queue `PromiseJobs`, more often referred to as the "microtask queue" (ES8 term).
|
||||
|
||||
As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
|
||||
As stated in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
|
||||
|
||||
- The queue is first-in-first-out: tasks enqueued first are run first.
|
||||
- Execution of a task is initiated only when nothing else is running.
|
||||
|
||||
Or, to say that simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue. They are not executed yet. When the JavaScript engine becomes free from the current code, it takes a task from the queue and executes it.
|
||||
Or, to say more simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue; they are not executed yet. When the JavaScript engine becomes free from the current code, it takes a task from the queue and executes it.
|
||||
|
||||
That's why "code finished" in the example above shows first.
|
||||
|
||||
|
@ -38,7 +38,7 @@ That's why "code finished" in the example above shows first.
|
|||
|
||||
Promise handlers always go through this internal queue.
|
||||
|
||||
If there's a chain with multiple `.then/catch/finally`, then every one of them is executed asynchronously. That is, it first gets queued, and executed when the current code is complete and previously queued handlers are finished.
|
||||
If there's a chain with multiple `.then/catch/finally`, then every one of them is executed asynchronously. That is, it first gets queued, then executed when the current code is complete and previously queued handlers are finished.
|
||||
|
||||
**What if the order matters for us? How can we make `code finished` run after `promise done`?**
|
||||
|
||||
|
@ -58,7 +58,7 @@ Remember the `unhandledrejection` event from the chapter <info:promise-error-han
|
|||
|
||||
Now we can see exactly how JavaScript finds out that there was an unhandled rejection.
|
||||
|
||||
**"Unhandled rejection" occurs when a promise error is not handled at the end of the microtask queue.**
|
||||
**An "unhandled rejection" occurs when a promise error is not handled at the end of the microtask queue.**
|
||||
|
||||
Normally, if we expect an error, we add `.catch` to the promise chain to handle it:
|
||||
|
||||
|
@ -72,7 +72,7 @@ promise.catch(err => alert('caught'));
|
|||
window.addEventListener('unhandledrejection', event => alert(event.reason));
|
||||
```
|
||||
|
||||
...But if we forget to add `.catch`, then, after the microtask queue is empty, the engine triggers the event:
|
||||
But if we forget to add `.catch`, then, after the microtask queue is empty, the engine triggers the event:
|
||||
|
||||
```js run
|
||||
let promise = Promise.reject(new Error("Promise Failed!"));
|
||||
|
@ -93,20 +93,20 @@ setTimeout(() => promise.catch(err => alert('caught')), 1000);
|
|||
window.addEventListener('unhandledrejection', event => alert(event.reason));
|
||||
```
|
||||
|
||||
Now, if you run it, we'll see `Promise Failed!` first and then `caught`.
|
||||
Now, if we run it, we'll see `Promise Failed!` first and then `caught`.
|
||||
|
||||
If we didn't know about the microtasks queue, we could wonder: "Why did `unhandledrejection` handler run? We did catch the error!".
|
||||
If we didn't know about the microtasks queue, we could wonder: "Why did `unhandledrejection` handler run? We did catch and handle the error!"
|
||||
|
||||
But now we understand that `unhandledrejection` is generated when the microtask queue is complete: the engine examines promises and, if any of them is in "rejected" state, then the event triggers.
|
||||
But now we understand that `unhandledrejection` is generated when the microtask queue is complete: the engine examines promises and, if any of them is in the "rejected" state, then the event triggers.
|
||||
|
||||
In the example above, `.catch` added by `setTimeout` also triggers, but later, after `unhandledrejection` has already occurred, so that doesn't change anything.
|
||||
In the example above, `.catch` added by `setTimeout` also triggers. But it does so later, after `unhandledrejection` has already occurred, so it doesn't change anything.
|
||||
|
||||
## Summary
|
||||
|
||||
Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
|
||||
Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (ES8 term).
|
||||
|
||||
So, `.then/catch/finally` handlers are always called after the current code is finished.
|
||||
So `.then/catch/finally` handlers are always called after the current code is finished.
|
||||
|
||||
If we need to guarantee that a piece of code is executed after `.then/catch/finally`, we can add it into a chained `.then` call.
|
||||
|
||||
In most Javascript engines, including browsers and Node.js, the concept of microtasks is closely tied with "event loop" and "macrotasks". As these have no direct relation to promises, they are covered in another part of the tutorial, in the chapter <info:event-loop>.
|
||||
In most Javascript engines, including browsers and Node.js, the concept of microtasks is closely tied with the "event loop" and "macrotasks". As these have no direct relation to promises, they are covered in another part of the tutorial, in the chapter <info:event-loop>.
|
||||
|
|
|
@ -14,7 +14,7 @@ async function f() {
|
|||
|
||||
The word "async" before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically.
|
||||
|
||||
For instance, this function returns a resolved promise with the result of `1`, let's test it:
|
||||
For instance, this function returns a resolved promise with the result of `1`; let's test it:
|
||||
|
||||
```js run
|
||||
async function f() {
|
||||
|
@ -24,7 +24,7 @@ async function f() {
|
|||
f().then(alert); // 1
|
||||
```
|
||||
|
||||
...We could explicitly return a promise, that would be the same:
|
||||
...We could explicitly return a promise, which would be the same:
|
||||
|
||||
```js run
|
||||
async function f() {
|
||||
|
@ -67,7 +67,7 @@ f();
|
|||
|
||||
The function execution "pauses" at the line `(*)` and resumes when the promise settles, with `result` becoming its result. So the code above shows "done!" in one second.
|
||||
|
||||
Let's emphasize: `await` literally makes JavaScript wait until the promise settles, and then go on with the result. That doesn't cost any CPU resources, because the engine can do other jobs meanwhile: execute other scripts, handle events etc.
|
||||
Let's emphasize: `await` literally makes JavaScript wait until the promise settles, and then go on with the result. That doesn't cost any CPU resources, because the engine can do other jobs in the meantime: execute other scripts, handle events, etc.
|
||||
|
||||
It's just a more elegant syntax of getting the promise result than `promise.then`, easier to read and write.
|
||||
|
||||
|
@ -130,7 +130,7 @@ let response = await fetch('/article/promise-chaining/user.json');
|
|||
let user = await response.json();
|
||||
```
|
||||
|
||||
We can wrap it into an anonymous async function, like this:
|
||||
But we can wrap it into an anonymous async function, like this:
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
|
@ -143,9 +143,9 @@ We can wrap it into an anonymous async function, like this:
|
|||
|
||||
````
|
||||
````smart header="`await` accepts \"thenables\""
|
||||
Like `promise.then`, `await` allows to use thenable objects (those with a callable `then` method). The idea is that a 3rd-party object may not be a promise, but promise-compatible: if it supports `.then`, that's enough to use with `await`.
|
||||
Like `promise.then`, `await` allows to use thenable objects (those with a callable `then` method). The idea is that a third-party object may not be a promise, but promise-compatible: if it supports `.then`, that's enough to use with `await`.
|
||||
|
||||
Here's a demo `Thenable` class, the `await` below accepts its instances:
|
||||
Here's a demo `Thenable` class; the `await` below accepts its instances:
|
||||
|
||||
```js run
|
||||
class Thenable {
|
||||
|
@ -168,7 +168,7 @@ async function f() {
|
|||
f();
|
||||
```
|
||||
|
||||
If `await` gets a non-promise object with `.then`, it calls that method providing built-in functions `resolve`, `reject` as arguments (just as it does for a regular `Promise` executor). Then `await` waits until one of them is called (in the example above it happens in the line `(*)`) and then proceeds with the result.
|
||||
If `await` gets a non-promise object with `.then`, it calls that method providing the built-in functions `resolve` and `reject` as arguments (just as it does for a regular `Promise` executor). Then `await` waits until one of them is called (in the example above it happens in the line `(*)`) and then proceeds with the result.
|
||||
````
|
||||
|
||||
````smart header="Async class methods"
|
||||
|
@ -192,7 +192,7 @@ The meaning is the same: it ensures that the returned value is a promise and ena
|
|||
````
|
||||
## Error handling
|
||||
|
||||
If a promise resolves normally, then `await promise` returns the result. But in case of a rejection, it throws the error, just as if there were a `throw` statement at that line.
|
||||
If a promise resolves normally, then `await promise` returns the result. But in the case of a rejection, it throws the error, just as if there were a `throw` statement at that line.
|
||||
|
||||
This code:
|
||||
|
||||
|
@ -204,7 +204,7 @@ async function f() {
|
|||
}
|
||||
```
|
||||
|
||||
...Is the same as this:
|
||||
...is the same as this:
|
||||
|
||||
```js
|
||||
async function f() {
|
||||
|
@ -233,7 +233,7 @@ async function f() {
|
|||
f();
|
||||
```
|
||||
|
||||
In case of an error, the control jumps to the `catch` block. We can also wrap multiple lines:
|
||||
In the case of an error, the control jumps to the `catch` block. We can also wrap multiple lines:
|
||||
|
||||
```js run
|
||||
async function f() {
|
||||
|
@ -263,15 +263,13 @@ f().catch(alert); // TypeError: failed to fetch // (*)
|
|||
*/!*
|
||||
```
|
||||
|
||||
If we forget to add `.catch` there, then we get an unhandled promise error (viewable in the console). We can catch such errors using a global event handler as described in the chapter <info:promise-error-handling>.
|
||||
If we forget to add `.catch` there, then we get an unhandled promise error (viewable in the console). We can catch such errors using a global `unhandledrejection` event handler as described in the chapter <info:promise-error-handling>.
|
||||
|
||||
|
||||
```smart header="`async/await` and `promise.then/catch`"
|
||||
When we use `async/await`, we rarely need `.then`, because `await` handles the waiting for us. And we can use a regular `try..catch` instead of `.catch`. That's usually (not always) more convenient.
|
||||
When we use `async/await`, we rarely need `.then`, because `await` handles the waiting for us. And we can use a regular `try..catch` instead of `.catch`. That's usually (but not always) more convenient.
|
||||
|
||||
But at the top level of the code, when we're outside of any `async` function, we're syntactically unable to use `await`, so it's a normal practice to add `.then/catch` to handle the final result or falling-through errors.
|
||||
|
||||
Like in the line `(*)` of the example above.
|
||||
But at the top level of the code, when we're outside any `async` function, we're syntactically unable to use `await`, so it's a normal practice to add `.then/catch` to handle the final result or falling-through error, like in the line `(*)` of the example above.
|
||||
```
|
||||
|
||||
````smart header="`async/await` works well with `Promise.all`"
|
||||
|
@ -286,7 +284,7 @@ let results = await Promise.all([
|
|||
]);
|
||||
```
|
||||
|
||||
In case of an error, it propagates as usual: from the failed promise to `Promise.all`, and then becomes an exception that we can catch using `try..catch` around the call.
|
||||
In the case of an error, it propagates as usual, from the failed promise to `Promise.all`, and then becomes an exception that we can catch using `try..catch` around the call.
|
||||
|
||||
````
|
||||
|
||||
|
@ -295,13 +293,13 @@ In case of an error, it propagates as usual: from the failed promise to `Promise
|
|||
The `async` keyword before a function has two effects:
|
||||
|
||||
1. Makes it always return a promise.
|
||||
2. Allows to use `await` in it.
|
||||
2. Allows `await` to be used in it.
|
||||
|
||||
The `await` keyword before a promise makes JavaScript wait until that promise settles, and then:
|
||||
|
||||
1. If it's an error, the exception is generated, same as if `throw error` were called at that very place.
|
||||
1. If it's an error, the exception is generated — same as if `throw error` were called at that very place.
|
||||
2. Otherwise, it returns the result.
|
||||
|
||||
Together they provide a great framework to write asynchronous code that is easy both to read and write.
|
||||
Together they provide a great framework to write asynchronous code that is easy to both read and write.
|
||||
|
||||
With `async/await` we rarely need to write `promise.then/catch`, but we still shouldn't forget that they are based on promises, because sometimes (e.g. in the outermost scope) we have to use these methods. Also `Promise.all` is a nice thing to wait for many tasks simultaneously.
|
||||
With `async/await` we rarely need to write `promise.then/catch`, but we still shouldn't forget that they are based on promises, because sometimes (e.g. in the outermost scope) we have to use these methods. Also `Promise.all` is nice when we are waiting for many tasks simultaneously.
|
||||
|
|
|
@ -140,7 +140,7 @@ for(let value of generator) {
|
|||
}
|
||||
```
|
||||
|
||||
As generators are iterable, we can call all related functionality, e.g. the spread operator `...`:
|
||||
As generators are iterable, we can call all related functionality, e.g. the spread syntax `...`:
|
||||
|
||||
```js run
|
||||
function* generateSequence() {
|
||||
|
@ -154,7 +154,7 @@ let sequence = [0, ...generateSequence()];
|
|||
alert(sequence); // 0, 1, 2, 3
|
||||
```
|
||||
|
||||
In the code above, `...generateSequence()` turns the iterable generator object into an array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator))
|
||||
In the code above, `...generateSequence()` turns the iterable generator object into an array of items (read more about the spread syntax in the chapter [](info:rest-parameters-spread#spread-syntax))
|
||||
|
||||
## Using generators for iterables
|
||||
|
||||
|
|
|
@ -120,11 +120,10 @@ Here's a small cheatsheet:
|
|||
| `next()` return value is | any value | `Promise` |
|
||||
| to loop, use | `for..of` | `for await..of` |
|
||||
|
||||
|
||||
````warn header="The spread operator `...` doesn't work asynchronously"
|
||||
````warn header="The spread syntax `...` doesn't work asynchronously"
|
||||
Features that require regular, synchronous iterators, don't work with asynchronous ones.
|
||||
|
||||
For instance, a spread operator won't work:
|
||||
For instance, a spread syntax won't work:
|
||||
```js
|
||||
alert( [...range] ); // Error, no Symbol.iterator
|
||||
```
|
||||
|
|
|
@ -73,7 +73,7 @@ Let's curry it!
|
|||
log = _.curry(log);
|
||||
```
|
||||
|
||||
After that `log` work normally:
|
||||
After that `log` works normally:
|
||||
|
||||
```js
|
||||
log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
The solution is short, yet may look a bit tricky, so here I provide it with extensive comments:
|
||||
|
||||
|
||||
```js
|
||||
let sortedRows = Array.from(table.tBodies[0].rows) // (1)
|
||||
.sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1); // (2)
|
||||
let sortedRows = Array.from(table.tBodies[0].rows) // 1
|
||||
.sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML));
|
||||
|
||||
table.tBodies[0].append(...sortedRows); // (3)
|
||||
```
|
||||
|
@ -14,6 +13,6 @@ The step-by-step algorthm:
|
|||
2. Then sort them comparing by the content of the first `<td>` (the name field).
|
||||
3. Now insert nodes in the right order by `.append(...sortedRows)`.
|
||||
|
||||
Please note: we don't have to remove row elements, just "re-insert", they leave the old place automatically.
|
||||
We don't have to remove row elements, just "re-insert", they leave the old place automatically.
|
||||
|
||||
Also note: even if the table HTML doesn't have `<tbody>`, the DOM structure always has it. So we must insert elements as `table.tBodes[0].append(...)`: a simple `table.append(...)` would fail.
|
||||
P.S. In our case, there's an explicit `<tbody>` in the table, but even if HTML table doesn't have `<tbody>`, the DOM structure always has it.
|
||||
|
|
|
@ -1,37 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<table id="table">
|
||||
<table id="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Surname</th>
|
||||
<th>Age</th>
|
||||
<th>Name</th><th>Surname</th><th>Age</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>John</td><td>Smith</td><td>10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>John</td>
|
||||
<td>Smith</td>
|
||||
<td>10</td>
|
||||
<td>Pete</td><td>Brown</td><td>15</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pete</td>
|
||||
<td>Brown</td>
|
||||
<td>15</td>
|
||||
<td>Ann</td><td>Lee</td><td>5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ann</td>
|
||||
<td>Lee</td>
|
||||
<td>5</td>
|
||||
<td>...</td><td>...</td><td>...</td>
|
||||
</tr>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
let sortedRows = Array.from(table.rows)
|
||||
.slice(1)
|
||||
.sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1);
|
||||
<script>
|
||||
let sortedRows = Array.from(table.tBodies[0].rows)
|
||||
.sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML));
|
||||
|
||||
table.tBodies[0].append(...sortedRows);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</script>
|
||||
|
|
|
@ -1,33 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<table id="table">
|
||||
<table id="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Surname</th>
|
||||
<th>Age</th>
|
||||
<th>Name</th><th>Surname</th><th>Age</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>John</td><td>Smith</td><td>10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>John</td>
|
||||
<td>Smith</td>
|
||||
<td>10</td>
|
||||
<td>Pete</td><td>Brown</td><td>15</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pete</td>
|
||||
<td>Brown</td>
|
||||
<td>15</td>
|
||||
<td>Ann</td><td>Lee</td><td>5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ann</td>
|
||||
<td>Lee</td>
|
||||
<td>5</td>
|
||||
<td>...</td><td>...</td><td>...</td>
|
||||
</tr>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
<script>
|
||||
// ... your code ...
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</script>
|
||||
|
|
|
@ -6,33 +6,29 @@ importance: 5
|
|||
|
||||
There's a table:
|
||||
|
||||
```html run
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Surname</th>
|
||||
<th>Age</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>John</td>
|
||||
<td>Smith</td>
|
||||
<td>10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pete</td>
|
||||
<td>Brown</td>
|
||||
<td>15</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ann</td>
|
||||
<td>Lee</td>
|
||||
<td>5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
</tr>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th><th>Surname</th><th>Age</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>John</td><td>Smith</td><td>10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pete</td><td>Brown</td><td>15</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ann</td><td>Lee</td><td>5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>...</td><td>...</td><td>...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
There may be more rows in it.
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ If there are some actions upon leaving the parent element, e.g. an animation run
|
|||
|
||||
To avoid it, we can check `relatedTarget` in the handler and, if the mouse is still inside the element, then ignore such event.
|
||||
|
||||
Alternatively we can use other events: `mouseenter` и `mouseleave`, that we'll be covering now, as they don't have such problems.
|
||||
Alternatively we can use other events: `mouseenter` and `mouseleave`, that we'll be covering now, as they don't have such problems.
|
||||
|
||||
## Events mouseenter and mouseleave
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ window.onbeforeunload = function() {
|
|||
};
|
||||
```
|
||||
|
||||
For historical reasons, returning a non-empty string also counts as canceling the event. Some time ago browsers used show it as a message, but as the [modern specification](https://html.spec.whatwg.org/#unloading-documents) says, they shouldn't.
|
||||
For historical reasons, returning a non-empty string also counts as canceling the event. Some time ago browsers used to show it as a message, but as the [modern specification](https://html.spec.whatwg.org/#unloading-documents) says, they shouldn't.
|
||||
|
||||
Here's an example:
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ That's for historical reasons.
|
|||
|
||||
There's a rule: scripts from one site can't access contents of the other site. So, e.g. a script at `https://facebook.com` can't read the user's mailbox at `https://gmail.com`.
|
||||
|
||||
Or, to be more precise, one origin (domain/port/protocol triplet) can't access the content from another one. So even if we have a subdomain, or just another port, these are different origins, no access to each other.
|
||||
Or, to be more precise, one origin (domain/port/protocol triplet) can't access the content from another one. So even if we have a subdomain, or just another port, these are different origins with no access to each other.
|
||||
|
||||
This rule also affects resources from other domains.
|
||||
|
||||
|
@ -159,7 +159,7 @@ Details may vary depending on the browser, but the idea is the same: any informa
|
|||
|
||||
Why do we need error details?
|
||||
|
||||
There are many services (and we can build our own) that listen for global errors using `window.onerror`, save errors and provide an interface to access and analyze them. That's great, as we can see real errors, triggered by our users. But if a script comes from another origin, then there's no much information about errors in it, as we've just seen.
|
||||
There are many services (and we can build our own) that listen for global errors using `window.onerror`, save errors and provide an interface to access and analyze them. That's great, as we can see real errors, triggered by our users. But if a script comes from another origin, then there's not much information about errors in it, as we've just seen.
|
||||
|
||||
Similar cross-origin policy (CORS) is enforced for other types of resources as well.
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ libs:
|
|||
|
||||
In this chapter we'll cover selection in the document, as well as selection in form fields, such as `<input>`.
|
||||
|
||||
JavaScript can do get the existing selection, select/deselect both as a whole or partially, remove the selected part from the document, wrap it into a tag, and so on.
|
||||
JavaScript can get the existing selection, select/deselect both as a whole or partially, remove the selected part from the document, wrap it into a tag, and so on.
|
||||
|
||||
You can get ready to use recipes at the end, in "Summary" section. But you'll get much more if you read the whole chapter. The underlying `Range` and `Selection` objects are easy to grasp, and then you'll need no recipes to make them do what you want.
|
||||
|
||||
|
@ -16,7 +16,7 @@ You can get ready to use recipes at the end, in "Summary" section. But you'll ge
|
|||
|
||||
The basic concept of selection is [Range](https://dom.spec.whatwg.org/#ranges): basically, a pair of "boundary points": range start and range end.
|
||||
|
||||
Each point represented as a parent DOM node with the relative offset from its start. If the parent node is an element element node, then the offset is a child number, for a text node it's the position in the text. Examples to follow.
|
||||
Each point represented as a parent DOM node with the relative offset from its start. If the parent node is an element node, then the offset is a child number, for a text node it's the position in the text. Examples to follow.
|
||||
|
||||
Let's select something.
|
||||
|
||||
|
@ -95,8 +95,8 @@ Let's select `"Example: <i>italic</i>"`. That's two first children of `<p>` (cou
|
|||
</script>
|
||||
```
|
||||
|
||||
- `range.setStart(p, 0)` -- sets the start at the 0th child of `<p>` (that's a text node `"Example: "`).
|
||||
- `range.setEnd(p, 2)` -- spans the range up to (but not including) 2nd child of `<p>` (that's a text node `" and "`, but as the end is not included, so the last selected node is `<i>`).
|
||||
- `range.setStart(p, 0)` -- sets the start at the 0th child of `<p>` (that's the text node `"Example: "`).
|
||||
- `range.setEnd(p, 2)` -- spans the range up to (but not including) 2nd child of `<p>` (that's the text node `" and "`, but as the end is not included, so the last selected node is `<i>`).
|
||||
|
||||
Here's a more flexible test stand where you try more variants:
|
||||
|
||||
|
@ -387,7 +387,7 @@ Also, there are convenience methods to manipulate the selection range directly,
|
|||
- `setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)` - replace selection range with the given start `anchorNode/anchorOffset` and end `focusNode/focusOffset`. All content in-between them is selected.
|
||||
- `selectAllChildren(node)` -- select all children of the `node`.
|
||||
- `deleteFromDocument()` -- remove selected content from the document.
|
||||
- `containsNode(node, allowPartialContainment = false)` -- checks whether the selection contains `node` (partically if the second argument is `true`)
|
||||
- `containsNode(node, allowPartialContainment = false)` -- checks whether the selection contains `node` (partially if the second argument is `true`)
|
||||
|
||||
So, for many tasks we can call `Selection` methods, no need to access the underlying `Range` object.
|
||||
|
||||
|
@ -551,7 +551,7 @@ If nothing is selected, or we use equal `start` and `end` in `setRangeText`, the
|
|||
|
||||
We can also insert something "at the cursor" using `setRangeText`.
|
||||
|
||||
Here's an button that inserts `"HELLO"` at the cursor position and puts the cursor immediately after it. If the selection is not empty, then it gets replaced (we can do detect in by comparing `selectionStart!=selectionEnd` and do something else instead):
|
||||
Here's a button that inserts `"HELLO"` at the cursor position and puts the cursor immediately after it. If the selection is not empty, then it gets replaced (we can detect it by comparing `selectionStart!=selectionEnd` and do something else instead):
|
||||
|
||||
```html run autorun
|
||||
<input id="input" style="width:200px" value="Text Text Text Text Text">
|
||||
|
@ -583,7 +583,7 @@ To make something unselectable, there are three ways:
|
|||
|
||||
This doesn't allow the selection to start at `elem`. But the user may start the selection elsewhere and include `elem` into it.
|
||||
|
||||
Then `elem` will become a part of `document.getSelection()`, so the selection actully happens, but its content is usually ignored in copy-paste.
|
||||
Then `elem` will become a part of `document.getSelection()`, so the selection actually happens, but its content is usually ignored in copy-paste.
|
||||
|
||||
|
||||
2. Prevent default action in `onselectstart` or `mousedown` events.
|
||||
|
@ -632,7 +632,7 @@ The most used recipes are probably:
|
|||
cloned.append(selection.getRangeAt(i).cloneContents());
|
||||
}
|
||||
```
|
||||
2. Setting the selection
|
||||
2. Setting the selection:
|
||||
```js run
|
||||
let selection = document.getSelection();
|
||||
|
||||
|
|
|
@ -335,5 +335,5 @@ That's a way to run code in another, parallel thread.
|
|||
|
||||
Web Workers can exchange messages with the main process, but they have their own variables, and their own event loop.
|
||||
|
||||
Web Workers do not have access to DOM, so they are useful, mainly, for calculations, to use multiplle CPU cores simultaneously.
|
||||
Web Workers do not have access to DOM, so they are useful, mainly, for calculations, to use multiple CPU cores simultaneously.
|
||||
```
|
||||
|
|
|
@ -335,10 +335,6 @@ The full example:
|
|||
|
||||
[codetabs src="postmessage" height=120]
|
||||
|
||||
```smart header="There's no delay"
|
||||
There's totally no delay between `postMessage` and the `message` event. The event triggers synchronously, faster than `setTimeout(...,0)`.
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
To call methods and access the content of another window, we should first have a reference to it.
|
||||
|
|
|
@ -107,6 +107,6 @@ Let's explain that step-by-step:
|
|||
let blob = new Blob(chunks);
|
||||
```
|
||||
|
||||
At we end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process.
|
||||
At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process.
|
||||
|
||||
Once again, please note, that's not for *upload* progress (no way now with `fetch`), only for *download* progress.
|
||||
|
|
|
@ -44,7 +44,7 @@ To resume upload, we need to know *exactly* the number of bytes received by the
|
|||
|
||||
This assumes that the server tracks file uploads by `X-File-Id` header. Should be implemented at server-side.
|
||||
|
||||
If the file don't yet exist at the server, then the server response should be `0`
|
||||
If the file doesn't yet exist at the server, then the server response should be `0`
|
||||
|
||||
3. Then, we can use `Blob` method `slice` to send the file from `startByte`:
|
||||
```js
|
||||
|
|
|
@ -470,9 +470,9 @@ Methods that involve searching support either exact keys or so-called "range que
|
|||
|
||||
Ranges are created using following calls:
|
||||
|
||||
- `IDBKeyRange.lowerBound(lower, [open])` means: `>lower` (or `≥lower` if `open` is true)
|
||||
- `IDBKeyRange.upperBound(upper, [open])` means: `<upper` (or `≤upper` if `open` is true)
|
||||
- `IDBKeyRange.bound(lower, upper, [lowerOpen], [upperOpen])` means: between `lower` and `upper`, with optional equality if the corresponding `open` is true.
|
||||
- `IDBKeyRange.lowerBound(lower, [open])` means: `≥lower` (or `>lower` if `open` is true)
|
||||
- `IDBKeyRange.upperBound(upper, [open])` means: `≤upper` (or `<upper` if `open` is true)
|
||||
- `IDBKeyRange.bound(lower, upper, [lowerOpen], [upperOpen])` means: between `lower` and `upper`. If the open flags is true, the corresponding key is not included in the range.
|
||||
- `IDBKeyRange.only(key)` -- a range that consists of only one `key`, rarely used.
|
||||
|
||||
All searching methods accept a `query` argument that can be either an exact key or a key range:
|
||||
|
@ -491,16 +491,16 @@ Request examples:
|
|||
// get one book
|
||||
books.get('js')
|
||||
|
||||
// get books with 'css' < id < 'html'
|
||||
// get books with 'css' <= id <= 'html'
|
||||
books.getAll(IDBKeyRange.bound('css', 'html'))
|
||||
|
||||
// get books with 'html' <= id
|
||||
// get books with 'html' < id
|
||||
books.getAll(IDBKeyRange.lowerBound('html', true))
|
||||
|
||||
// get all books
|
||||
books.getAll()
|
||||
|
||||
// get all keys: id >= 'js'
|
||||
// get all keys: id > 'js'
|
||||
books.getAllKeys(IDBKeyRange.lowerBound('js', true))
|
||||
```
|
||||
|
||||
|
@ -580,7 +580,7 @@ request.onsuccess = function() {
|
|||
We can also use `IDBKeyRange` to create ranges and looks for cheap/expensive books:
|
||||
|
||||
```js
|
||||
// find books where price < 5
|
||||
// find books where price <= 5
|
||||
let request = priceIndex.getAll(IDBKeyRange.upperBound(5));
|
||||
```
|
||||
|
||||
|
|
|
@ -397,4 +397,4 @@ Custom elements can be of two types:
|
|||
/* <button is="my-button"> */
|
||||
```
|
||||
|
||||
Custom elements are well-supported among browsers. Edge is a bit behind, but there's a polyfill <https://github.com/webcomponents/webcomponentsjs>.
|
||||
Custom elements are well-supported among browsers. Edge is a bit behind, but there's a polyfill <https://github.com/webcomponents/polyfills/tree/master/packages/webcomponentsjs>.
|
||||
|
|
|
@ -103,11 +103,11 @@ The result is called "flattened" DOM:
|
|||
|
||||
...But the flattened DOM exists only for rendering and event-handling purposes. It's kind of "virtual". That's how things are shown. But the nodes in the document are actually not moved around!
|
||||
|
||||
That can be easily checked if we run `querySelector`: nodes are still at their places.
|
||||
That can be easily checked if we run `querySelectorAll`: nodes are still at their places.
|
||||
|
||||
```js
|
||||
// light DOM <span> nodes are still at the same place, under `<user-card>`
|
||||
alert( document.querySelector('user-card span').length ); // 2
|
||||
alert( document.querySelectorAll('user-card span').length ); // 2
|
||||
```
|
||||
|
||||
So, the flattened DOM is derived from shadow DOM by inserting slots. The browser renders it and uses for style inheritance, event propagation (more about that later). But JavaScript still sees the document "as is", before flattening.
|
||||
|
|
|
@ -34,7 +34,7 @@ Unlike strings, regular expressions have flag `pattern:u` that fixes such proble
|
|||
## Unicode properties \p{...}
|
||||
|
||||
```warn header="Not supported in Firefox and Edge"
|
||||
Despite being a part of the standard since 2018, unicode proeprties are not supported in Firefox ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1361876)) and Edge ([bug](https://github.com/Microsoft/ChakraCore/issues/2969)).
|
||||
Despite being a part of the standard since 2018, unicode properties are not supported in Firefox ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1361876)) and Edge ([bug](https://github.com/Microsoft/ChakraCore/issues/2969)).
|
||||
|
||||
There's [XRegExp](http://xregexp.com) library that provides "extended" regular expressions with cross-browser support for unicode properties.
|
||||
```
|
||||
|
|