Merge pull request #5 from javascript-tutorial/master

Updated Repo
This commit is contained in:
Aniket Kudale 2020-02-20 15:16:35 +05:30 committed by GitHub
commit 4f6356bcca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 179 additions and 189 deletions

View file

@ -63,7 +63,7 @@ JavaScript's abilities in the browser are limited for the sake of the user's saf
Examples of such restrictions include:
- JavaScript on a webpage may not read/write arbitrary files on the hard disk, copy them or execute programs. It has no direct access to OS system functions.
- JavaScript on a webpage may not read/write arbitrary files on the hard disk, copy them or execute programs. It has no direct access to OS functions.
Modern browsers allow it to work with files, but the access is limited and only provided if the user does certain actions, like "dropping" a file into a browser window or selecting it via an `<input>` tag.

View file

@ -94,7 +94,7 @@ But it should be two separate statements, not one. Such a merging in this case i
We recommend putting semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again -- *it is possible* to leave out semicolons most of the time. But it's safer -- especially for a beginner -- to use them.
## Comments
## Comments [#code-comments]
As time goes on, programs become more and more complex. It becomes necessary to add *comments* which describe what the code does and why.
@ -136,7 +136,7 @@ alert('World');
```
```smart header="Use hotkeys!"
In most editors, a line of code can be commented out by pressing the `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac, try `key:Cmd` instead of `key:Ctrl`.
In most editors, a line of code can be commented out by pressing the `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac, try `key:Cmd` instead of `key:Ctrl` and `key:Option` instead of `key:Shift`.
```
````warn header="Nested comments are not supported!"

View file

@ -138,10 +138,10 @@ Here's an extract from the [precedence table](https://developer.mozilla.org/en/J
| Precedence | Name | Sign |
|------------|------|------|
| ... | ... | ... |
| 16 | unary plus | `+` |
| 16 | unary negation | `-` |
| 14 | multiplication | `*` |
| 14 | division | `/` |
| 17 | unary plus | `+` |
| 17 | unary negation | `-` |
| 15 | multiplication | `*` |
| 15 | division | `/` |
| 13 | addition | `+` |
| 13 | subtraction | `-` |
| ... | ... | ... |

View file

@ -256,7 +256,7 @@ For even values of `i`, the `continue` directive stops executing the body and pa
````smart header="The `continue` directive helps decrease nesting"
A loop that shows odd values could look like this:
```js
```js run
for (let i = 0; i < 10; i++) {
if (i % 2) {
@ -268,7 +268,7 @@ for (let i = 0; i < 10; i++) {
From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`.
But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of`if` is longer than a few lines, that may decrease the overall readability.
But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability.
````
````warn header="No `break/continue` to the right side of '?'"

View file

@ -86,7 +86,7 @@ For example:
```js
// backtick quotes ` allow to split the string into multiple lines
let str = `
Ecma International's TC39 is a group of JavaScript developers,
ECMA International's TC39 is a group of JavaScript developers,
implementers, academics, and more, collaborating with the community
to maintain and evolve the definition of JavaScript.
`;

View file

@ -101,7 +101,9 @@ For multiword properties, the dot access doesn't work:
user.likes birds = true
```
That's because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations.
JavaScript doesn't understand that. It thinks that we address `user.likes`, and then gives a syntax error when comes across unexpected `birds`.
The dot requires the key to be a valid variable identifier. That implies: contains no spaces, doesn't start with a digit and doesn't include special characters (`$` и `_` are allowed).
There's an alternative "square bracket notation" that works with any string:
@ -203,43 +205,6 @@ Square brackets are much more powerful than the dot notation. They allow any pro
So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.
````smart header="Reserved words are allowed as property names"
A variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc.
But for an object property, there's no such restriction. Any name is fine:
```js run
let obj = {
for: 1,
let: 2,
return: 3
};
alert( obj.for + obj.let + obj.return ); // 6
```
Basically, any name is allowed, but there's a special one: `"__proto__"` that gets special treatment for historical reasons. For instance, we can't set it to a non-object value:
```js run
let obj = {};
obj.__proto__ = 5;
alert(obj.__proto__); // [object Object], didn't work as intended
```
As we see from the code, the assignment to a primitive `5` is ignored.
That can become a source of bugs and even vulnerabilities if we intend to store arbitrary key-value pairs in an object, and allow a visitor to specify the keys.
In that case the visitor may choose `__proto__` as the key, and the assignment logic will be ruined (as shown above).
There is a way to make objects treat `__proto__` as a regular property, which we'll cover later, but first we need to know more about objects.
There's also another data structure [Map](info:map-set), that we'll learn in the chapter <info:map-set>, which supports arbitrary keys.
````
## Property value shorthand
In real code we often use existing variables as values for property names.
@ -284,7 +249,63 @@ let user = {
};
```
## Existence check
## Property names limitations
Property names (keys) must be either strings or symbols (a special type for identifiers, to be covered later).
Other types are automatically converted to strings.
For instance, a number `0` becomes a string `"0"` when used as a property key:
```js run
let obj = {
0: "test" // same as "0": "test"
};
// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)
```
**Reserved words are allowed as property names.**
As we already know, a variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc.
But for an object property, there's no such restriction. Any name is fine:
```js run
let obj = {
for: 1,
let: 2,
return: 3
};
alert( obj.for + obj.let + obj.return ); // 6
```
We can use any string as a key, but there's a special property named `__proto__` that gets special treatment for historical reasons.
For instance, we can't set it to a non-object value:
```js run
let obj = {};
obj.__proto__ = 5; // assign a number
alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended
```
As we see from the code, the assignment to a primitive `5` is ignored.
The nature of `__proto__` will be revealed in detail later in the chapter [](info:prototype-inheritance).
As for now, it's important to know that such behavior of `__proto__` can become a source of bugs and even vulnerabilities if we intend to store user-provided keys in an object.
The problem is that a visitor may choose `__proto__` as the key, and the assignment logic will be ruined (as shown above).
There are two workarounds for the problem:
1. Modify the object's behavior to treat `__proto__` as a regular property. We'll learn how to do it in the chapter [](info:prototype-methods).
2. Using [Map](info:map-set) data structure which supports arbitrary keys. We'll learn it in the chapter <info:map-set>.
## Property existence test, "in" operator
A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined:

View file

@ -18,7 +18,7 @@ let id = Symbol();
Upon creation, we can give symbol a description (also called a symbol name), mostly useful for debugging purposes:
```js run
```js
// id is a symbol with the description "id"
let id = Symbol("id");
```
@ -178,22 +178,6 @@ alert( clone[id] ); // 123
There's no paradox here. That's by design. The idea is that when we clone an object or merge objects, we usually want *all* properties to be copied (including symbols like `id`).
````smart header="Property keys of other types are coerced to strings"
We can only use strings or symbols as keys in objects. Other types are converted to strings.
For instance, a number `0` becomes a string `"0"` when used as a property key:
```js run
let obj = {
0: "test" // same as "0": "test"
};
// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)
```
````
## Global symbols
As we've seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property.
@ -241,7 +225,7 @@ alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
```
The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and return `undefined`.
The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and returns `undefined`.
That said, any symbols have `description` property.

View file

@ -534,7 +534,7 @@ The "right" algorithm to do string comparisons is more complex than it may seem,
So, the browser needs to know the language to compare.
Luckily, all modern browsers (IE10- requires the additional library [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf).
Luckily, all modern browsers (IE10- requires the additional library [Intl.js](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf).
It provides a special method to compare strings in different languages, following their rules.

View file

@ -10,15 +10,15 @@ The task is: find the contiguous subarray of `arr` with the maximal sum of items
Write the function `getMaxSubSum(arr)` that will return that sum.
For instance:
For instance:
```js
getMaxSubSum([-1, *!*2, 3*/!*, -9]) = 5 (the sum of highlighted items)
getMaxSubSum([*!*2, -1, 2, 3*/!*, -9]) = 6
getMaxSubSum([-1, 2, 3, -9, *!*11*/!*]) = 11
getMaxSubSum([-2, -1, *!*1, 2*/!*]) = 3
getMaxSubSum([*!*100*/!*, -9, 2, -3, 5]) = 100
getMaxSubSum([*!*1, 2, 3*/!*]) = 6 (take all)
getMaxSubSum([-1, *!*2, 3*/!*, -9]) == 5 (the sum of highlighted items)
getMaxSubSum([*!*2, -1, 2, 3*/!*, -9]) == 6
getMaxSubSum([-1, 2, 3, -9, *!*11*/!*]) == 11
getMaxSubSum([-2, -1, *!*1, 2*/!*]) == 3
getMaxSubSum([*!*100*/!*, -9, 2, -3, 5]) == 100
getMaxSubSum([*!*1, 2, 3*/!*]) == 6 (take all)
```
If all items are negative, it means that we take none (the subarray is empty), so the sum is zero:

View file

@ -20,7 +20,7 @@ let users = [
let usersById = groupById(users);
/*
// after the call we have:
// after the call we should have:
usersById = {
john: {id: 'john', name: "John Smith", age: 20}

View file

@ -12,7 +12,7 @@ If an object isn't technically an array, but represents a collection (list, set)
We can easily grasp the concept of iterables by making one of our own.
For instance, we have an object, that is not an array, but looks suitable for `for..of`.
For instance, we have an object that is not an array, but looks suitable for `for..of`.
Like a `range` object that represents an interval of numbers:
@ -294,7 +294,7 @@ Objects that can be used in `for..of` are called *iterable*.
- Technically, iterables must implement the method named `Symbol.iterator`.
- The result of `obj[Symbol.iterator]` is called an *iterator*. It handles the further iteration process.
- An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the iteration end, otherwise the `value` is the next value.
- An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value.
- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly.
- Built-in iterables like strings or arrays, also implement `Symbol.iterator`.
- String iterator knows about surrogate pairs.

View file

@ -141,7 +141,6 @@ And here's another part of the code, maybe another file using it:
let john = { name: "John" };
countUser(john); // count his visits
countUser(john);
// later john leaves us
john = null;

View file

@ -302,7 +302,7 @@ let company = {
salary: 1000
}, {
name: 'Alice',
salary: 600
salary: 1600
}],
development: {
@ -350,7 +350,7 @@ The algorithm is probably even easier to read from the code:
```js run
let company = { // the same object, compressed for brevity
sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 600 }],
sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }],
development: {
sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }],
internals: [{name: 'Jack', salary: 1300}]
@ -372,7 +372,7 @@ function sumSalaries(department) {
}
*/!*
alert(sumSalaries(company)); // 6700
alert(sumSalaries(company)); // 7700
```
The code is short and easy to understand (hopefully?). That's the power of recursion. It also works for any level of subdepartment nesting.

View file

@ -16,7 +16,7 @@ The prototype is a little bit "magical". When we want to read a property from `o
The property `[[Prototype]]` is internal and hidden, but there are many ways to set it.
One of them is to use `__proto__`, like this:
One of them is to use the special name `__proto__`, like this:
```js run
let animal = {

View file

@ -264,7 +264,7 @@ class User {
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name too short.
user = new User(""); // Name is too short.
```
The class declaration creates getters and setters in `User.prototype`, like this:

View file

@ -4,7 +4,7 @@ No matter how great we are at programming, sometimes our scripts have errors. Th
Usually, a script "dies" (immediately stops) in case of an error, printing it to console.
But there's a syntax construct `try..catch` that allows to "catch" errors and, instead of dying, do something more reasonable.
But there's a syntax construct `try..catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable.
## The "try..catch" syntax
@ -26,13 +26,13 @@ It works like this:
1. First, the code in `try {...}` is executed.
2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`.
3. If an error occurs, then `try` execution is stopped, and the control flows to the beginning of `catch(err)`. The `err` variable (can use any name for it) will contain an error object with details about what happened.
3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch(err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened.
![](try-catch-flow.svg)
So, an error inside the `try {…}` block does not kill the script: we have a chance to handle it in `catch`.
So, an error inside the `try {…}` block does not kill the script -- we have a chance to handle it in `catch`.
Let's see examples.
Let's look at some examples.
- An errorless example: shows `alert` `(1)` and `(2)`:
@ -87,7 +87,7 @@ try {
The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code.
So, `try..catch` can only handle errors that occur in the valid code. Such errors are called "runtime errors" or, sometimes, "exceptions".
So, `try..catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions".
````

View file

@ -252,7 +252,7 @@ fetch('/article/promise-chaining/user.json')
});
```
There is also a method `response.json()` that reads the remote data and parses it as JSON. In our case that's even more convenient, so let's switch to it.
The `response` object returned from `fetch` also includes the method `response.json()` that reads the remote data and parses it as JSON. In our case that's even more convenient, so let's switch to it.
We'll also use arrow functions for brevity:

View file

@ -92,7 +92,7 @@ new Promise((resolve, reject) => {
}).catch(alert); // ReferenceError: blabla is not defined
```
The final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
The final `.catch` not only catches explicit rejections, but also accidental errors in the handlers above.
## Rethrowing
@ -100,7 +100,7 @@ As we already noticed, `.catch` at the end of the chain is similar to `try..catc
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.
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 next closest successful `.then` handler.
In the example below the `.catch` successfully handles the error:

View file

@ -51,7 +51,7 @@ function promisify(f) {
return new Promise((resolve, reject) => {
function callback(err, result) { // our custom callback for f
if (err) {
return reject(err);
reject(err);
} else {
resolve(result);
}
@ -82,7 +82,7 @@ function promisify(f, manyArgs = false) {
return new Promise((resolve, reject) => {
function *!*callback(err, ...results*/!*) { // our custom callback for f
if (err) {
return reject(err);
reject(err);
} else {
// resolve with all callback results if manyArgs is specified
*!*resolve(manyArgs ? results : results[0]);*/!*

View file

@ -23,7 +23,7 @@ Why did the `.then` trigger afterwards? What's going on?
## Microtasks queue
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).
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 stated in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
@ -54,7 +54,7 @@ Now the order is as intended.
## Unhandled rejection
Remember the `unhandledrejection` event from the chapter <info:promise-error-handling>?
Remember the `unhandledrejection` event from the article <info:promise-error-handling>?
Now we can see exactly how JavaScript finds out that there was an unhandled rejection.
@ -109,4 +109,4 @@ So `.then/catch/finally` handlers are always called after the current code is fi
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 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>.
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 article <info:event-loop>.

View file

@ -15,6 +15,6 @@ function loadJson(url) {
})
}
loadJson('no-such-user.json') // (3)
loadJson('no-such-user.json')
.catch(alert); // Error: 404
```

View file

@ -143,7 +143,7 @@ But 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 third-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 us 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 it with `await`.
Here's a demo `Thenable` class; the `await` below accepts its instances:

View file

@ -102,7 +102,7 @@ But usually the first syntax is preferred, as the star `*` denotes that it's a g
As you probably already guessed looking at the `next()` method, generators are [iterable](info:iterable).
We can get loop over values by `for..of`:
We can loop over their values using `for..of`:
```js run
function* generateSequence() {
@ -314,11 +314,11 @@ alert(str); // 0..9A..Za..z
A generator composition is a natural way to insert a flow of one generator into another. It doesn't use extra memory to store intermediate results.
## "yield" is a two-way road
## "yield" is a two-way street
Until this moment, generators were similar to iterable objects, with a special syntax to generate values. But in fact they are much more powerful and flexible.
That's because `yield` is a two-way road: it not only returns the result outside, but also can pass the value inside the generator.
That's because `yield` is a two-way street: it not only returns the result to the outside, but also can pass the value inside the generator.
To do so, we should call `generator.next(arg)`, with an argument. That argument becomes the result of `yield`.
@ -347,7 +347,7 @@ generator.next(4); // --> pass the result into the generator
2. Then, as shown at the picture above, the result of `yield` gets into the `question` variable in the calling code.
3. On `generator.next(4)`, the generator resumes, and `4` gets in as the result: `let result = 4`.
Please note, the outer code does not have to immediately call`next(4)`. It may take time. That's not a problem: the generator will wait.
Please note, the outer code does not have to immediately call `next(4)`. It may take time. That's not a problem: the generator will wait.
For instance:

View file

@ -312,10 +312,10 @@ async function* fetchCommits(repo) {
}
```
1. We use the browser [fetch](info:fetch) method to download from a remote URL. It allows to supply authorization and other headers if needed, here GitHub requires `User-Agent`.
2. The fetch result is parsed as JSON, that's again a `fetch`-specific method.
3. We should get the next page URL from the `Link` header of the response. It has a special format, so we use a regexp for that. The next page URL may look like `https://api.github.com/repositories/93253246/commits?page=2`, it's generated by GitHub itself.
4. Then we yield all commits received, and when they finish -- the next `while(url)` iteration will trigger, making one more request.
1. We use the browser [fetch](info:fetch) method to download from a remote URL. It allows us to supply authorization and other headers if needed -- here GitHub requires `User-Agent`.
2. The fetch result is parsed as JSON. That's again a `fetch`-specific method.
3. We should get the next page URL from the `Link` header of the response. It has a special format, so we use a regexp for that. The next page URL may look like `https://api.github.com/repositories/93253246/commits?page=2`. It's generated by GitHub itself.
4. Then we yield all commits received, and when they finish, the next `while(url)` iteration will trigger, making one more request.
An example of use (shows commit authors in console):
@ -360,4 +360,4 @@ Syntax differences between async and regular generators:
In web-development we often meet streams of data, when it flows chunk-by-chunk. For instance, downloading or uploading a big file.
We can use async generators to process such data. It's also noteworthy that in some environments, such as browsers, there's also another API called Streams, that provides special interfaces to work with such streams, to transform the data and to pass it from one stream to another (e.g. download from one place and immediately send elsewhere).
We can use async generators to process such data. It's also noteworthy that in some environments, like in browsers, there's also another API called Streams, that provides special interfaces to work with such streams, to transform the data and to pass it from one stream to another (e.g. download from one place and immediately send elsewhere).

View file

@ -24,7 +24,7 @@ A module is just a file. One script is one module.
Modules can load each other and use special directives `export` and `import` to interchange functionality, call functions of one module from another one:
- `export` keyword labels variables and functions that should be accessible from outside the current module.
- `import` allows to import functionality from other modules.
- `import` allows the import of functionality from other modules.
For instance, if we have a file `sayHi.js` exporting a function:
@ -45,7 +45,7 @@ alert(sayHi); // function...
sayHi('John'); // Hello, John!
```
The `import` directive loads the module by path `./sayHi.js` relative the current file and assigns exported function `sayHi` to the corresponding variable.
The `import` directive loads the module by path `./sayHi.js` relative to the current file, and assigns exported function `sayHi` to the corresponding variable.
Let's run the example in-browser.
@ -85,7 +85,7 @@ Modules are expected to `export` what they want to be accessible from outside an
So we should import `user.js` into `hello.js` and get the required functionality from it instead of relying on global variables.
That's the correct variant:
This is the correct variant:
[codetabs src="scopes-working" height="140" current="hello.js"]
@ -110,7 +110,7 @@ If we really need to make a window-level global variable, we can explicitly assi
If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers.
That has important consequences. Let's see that on examples.
That has important consequences. Let's look at them using examples:
First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time:
@ -163,7 +163,7 @@ alert(admin.name); // Pete
So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that.
Such behavior allows to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready.
Such behavior allows us to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready.
For instance, the `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside:
@ -268,11 +268,11 @@ Please note: the second script actually runs before the first! So we'll see `und
That's because modules are deferred, so we wait for the document to be processed. The regular script runs immediately, so we see its output first.
When using modules, we should be aware that HTML-page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that.
When using modules, we should be aware that the HTML page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that.
### Async works on inline scripts
For non-module scripts, `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document.
For non-module scripts, the `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document.
For module scripts, it works on inline scripts as well.

View file

@ -439,7 +439,7 @@ sayHi();
import {sayHi} from './say.js'; // import at the end of the file
```
In practice imports are usually at the start of the file, but that's only for better convenience.
In practice imports are usually at the start of the file, but that's only for more convenience.
**Please note that import/export statements don't work if inside `{...}`.**

View file

@ -94,5 +94,5 @@ Dynamic imports work in regular scripts, they don't require `script type="module
```smart
Although `import()` looks like a function call, it's a special syntax that just happens to use parentheses (similar to `super()`).
So we can't copy `import` to a variable or use `call/apply` with it. That's not a function.
So we can't copy `import` to a variable or use `call/apply` with it. It's not a function.
```

View file

@ -2,7 +2,9 @@
A `Proxy` object wraps another object and intercepts operations, like reading/writing properties and others, optionally handling them on its own, or transparently allowing the object to handle them.
Proxies are used in many libraries and some browser frameworks. We'll see many practical applications in this chapter.
Proxies are used in many libraries and some browser frameworks. We'll see many practical applications in this article.
## Proxy
The syntax:
@ -244,7 +246,7 @@ If we forget to do it or return any falsy value, the operation triggers `TypeErr
Such methods differ in details:
- `Object.getOwnPropertyNames(obj)` returns non-symbol keys.
- `Object.getOwnPropertySymbols(obj)` returns symbol keys.
- `Object.keys/values()` returns non-symbol keys/values with `enumerable` flag (property flags were explained in the chapter <info:property-descriptors>).
- `Object.keys/values()` returns non-symbol keys/values with `enumerable` flag (property flags were explained in the article <info:property-descriptors>).
- `for..in` loops over non-symbol keys with `enumerable` flag, and also prototype keys.
...But all of them start with that list.
@ -446,14 +448,14 @@ Besides, an object may be proxied multiple times (multiple proxies may add diffe
So, such a proxy shouldn't be used everywhere.
```smart header="Private properties of a class"
Modern JavaScript engines natively support private properties in classes, prefixed with `#`. They are described in the chapter <info:private-protected-properties-methods>. No proxies required.
Modern JavaScript engines natively support private properties in classes, prefixed with `#`. They are described in the article <info:private-protected-properties-methods>. No proxies required.
Such properties have their own issues though. In particular, they are not inherited.
```
## "In range" with "has" trap
Let's see more examples.
Let's see more examples.ar
We have a range object:
@ -485,7 +487,7 @@ range = new Proxy(range, {
*!*
has(target, prop) {
*/!*
return prop >= target.start && prop <= target.end
return prop >= target.start && prop <= target.end;
}
});
@ -507,9 +509,9 @@ The `apply(target, thisArg, args)` trap handles calling a proxy as function:
- `thisArg` is the value of `this`.
- `args` is a list of arguments.
For example, let's recall `delay(f, ms)` decorator, that we did in the chapter <info:call-apply-decorators>.
For example, let's recall `delay(f, ms)` decorator, that we did in the article <info:call-apply-decorators>.
In that chapter we did it without proxies. A call to `delay(f, ms)` returned a function that forwards all calls to `f` after `ms` milliseconds.
In that article we did it without proxies. A call to `delay(f, ms)` returned a function that forwards all calls to `f` after `ms` milliseconds.
Here's the previous, function-based implementation:
@ -587,7 +589,7 @@ The result is the same, but now not only calls, but all operations on the proxy
We've got a "richer" wrapper.
Other traps exist: the full list is in the beginning of this chapter. Their usage pattern is similar to the above.
Other traps exist: the full list is in the beginning of this article. Their usage pattern is similar to the above.
## Reflect
@ -619,7 +621,7 @@ alert(user.name); // John
In particular, `Reflect` allows us to call operators (`new`, `delete`...) as functions (`Reflect.construct`, `Reflect.deleteProperty`, ...). That's an interesting capability, but here another thing is important.
**For every internal method, trappable by `Proxy`, there's a corresponding method in `Reflect`, with the same name and arguments as `Proxy` trap.**
**For every internal method, trappable by `Proxy`, there's a corresponding method in `Reflect`, with the same name and arguments as the `Proxy` trap.**
So we can use `Reflect` to forward an operation to the original object.

View file

@ -111,7 +111,7 @@ So:
## Advanced curry implementation
In case you'd like to get in details, here's the "advanced" curry implementation for multi-argument functions that we could use above.
In case you'd like to get in to the details, here's the "advanced" curry implementation for multi-argument functions that we could use above.
It's pretty short:
@ -175,7 +175,7 @@ For the call `curried(1)(2)(3)`:
2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together. As the argument count is still less than 3, `curry` returns `pass`.
3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function.
If that's still not obvious, just trace the calls sequence in your mind or on the paper.
If that's still not obvious, just trace the calls sequence in your mind or on paper.
```smart header="Fixed-length functions only"
The currying requires the function to have a fixed number of arguments.
@ -191,6 +191,6 @@ But most implementations of currying in JavaScript are advanced, as described: t
## Summary
*Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if arguments count is not enough.
*Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if the arguments count is not enough.
Currying allows to easily get partials. As we've seen in the logging example: the universal function `log(date, importance, message)` after currying gives us partials when called with one argument like `log(date)` or two arguments `log(date, importance)`.
Currying allows us to easily get partials. As we've seen in the logging example, after currying the three argument universal function `log(date, importance, message)` gives us partials when called with one argument (like `log(date)`) or two arguments (like `log(date, importance)`).

View file

@ -107,7 +107,7 @@ To emulate such behavior, a polyfill would need to analyze the code and replace
So, there's no well-known good polyfill.
Although, the other way around is proposed by the developers of [https://github.com/GoogleChromeLabs/jsbi](JSBI) library.
Although, the other way around is proposed by the developers of [JSBI](https://github.com/GoogleChromeLabs/jsbi) library.
This library implements big numbers using its own methods. We can use them instead of native bigints:

View file

@ -91,7 +91,7 @@ In practice though, `elem.getBoundingClientRect()` always returns positive width
```warn header="Internet Explorer and Edge: no support for `x/y`"
Internet Explorer and Edge don't support `x/y` properties for historical reasons.
So we can either make a polywill (add getters in `DomRect.prototype`) or just use `top/left`, as they are always the same as `x/y` for positive `width/height`, in particular in the result of `elem.getBoundingClientRect()`.
So we can either make a polyfill (add getters in `DomRect.prototype`) or just use `top/left`, as they are always the same as `x/y` for positive `width/height`, in particular in the result of `elem.getBoundingClientRect()`.
```
```warn header="Coordinates right/bottom are different from CSS position properties"

View file

@ -293,36 +293,20 @@ As we can see in the example above, we can set handlers *both* using a DOM-prope
````warn header="For some events, handlers only work with `addEventListener`"
There exist events that can't be assigned via a DOM-property. Must use `addEventListener`.
For instance, the event `transitionend` (CSS animation finished) is like that.
For instance, the event `DOMContentLoaded`, that triggers when the document is loaded and DOM is built.
Try the code below. In most browsers only the second handler works, not the first one.
```html run
<style>
input {
transition: width 1s;
width: 100px;
}
.wide {
width: 300px;
}
</style>
<input type="button" id="elem" onclick="this.classList.toggle('wide')" value="Click me">
<script>
elem.ontransitionend = function() {
alert("DOM property"); // doesn't work
};
*!*
elem.addEventListener("transitionend", function() {
alert("addEventListener"); // shows up when the animation finishes
});
*/!*
</script>
```js
document.onDOMContentLoaded = function() {
alert("DOM built"); // will never run
};
```
```js
document.addEventListener("DOMContentLoaded", function() {
alert("DOM built"); // this way it works
});
```
So `addEventListener` is more universal. Although, such events are an exception rather than the rule.
````
## Event object

View file

@ -121,7 +121,7 @@ The first idea may be to assign a separate handler to each button. But there's a
The handler reads the attribute and executes the method. Take a look at the working example:
```html autorun height=60 run
```html autorun height=60 run untrusted
<div id="menu">
<button data-action="save">Save</button>
<button data-action="load">Load</button>

View file

@ -300,4 +300,4 @@ We can lay a lot on this foundation.
- We can use event delegation for `mousedown/up`. A large-area event handler that checks `event.target` can manage Drag'n'Drop for hundreds of elements.
- And so on.
There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to described above, so it should be easy to understand them now. Or roll our own, as you can see that's easy enough to do, sometimes easier than adapting a third-part solution.
There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-part solution.

View file

@ -50,13 +50,13 @@ That was a theory. Now let's see how we can apply that knowledge.
Let's say we have a CPU-hungry task.
For example, syntax-highlighting (used to colorize code examples on this page) is quite CPU-heavy. To highlight the code, it performs the analysis, creates many colored elements, adds them to the document -- for a big text that takes a lot of time.
For example, syntax-highlighting (used to colorize code examples on this page) is quite CPU-heavy. To highlight the code, it performs the analysis, creates many colored elements, adds them to the document -- for a large amount of text that takes a lot of time.
While the engine is busy with syntax highlighting, it can't do other DOM-related stuff, process user events, etc. It may even cause the browser to "hiccup" or even "hang" for a bit, which is unacceptable.
We can evade problems by splitting the big task into pieces. Highlight first 100 lines, then schedule `setTimeout` (with zero-delay) another 100 lines, and so on.
We can avoid problems by splitting the big task into pieces. Highlight first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on.
To demonstrate the approach, for the sake of simplicity, instead of syntax-highlighting let's take a function that counts from `1` to `1000000000`.
To demonstrate this approach, for the sake of simplicity, instead of text-highlighting, let's take a function that counts from `1` to `1000000000`.
If you run the code below, the engine will "hang" for some time. For server-side JS that's clearly noticeable, and if you are running it in-browser, then try to click other buttons on the page -- you'll see that no other events get handled until the counting finishes.
@ -78,9 +78,9 @@ function count() {
count();
```
The browser may even show "the script takes too long" warning.
The browser may even show a "the script takes too long" warning.
Let's split the job using nested `setTimeout`:
Let's split the job using nested `setTimeout` calls:
```js run
let i = 0;
@ -113,13 +113,13 @@ A single run of `count` does a part of the job `(*)`, and then re-schedules itse
2. Second run counts: `i=1000001..2000000`.
3. ...and so on.
Now, if a new side task (e.g. `onclick` event) appears while the engine is busy executing part 1, it gets queued and then executes when part 1 finished, before the next part. Periodic returns to event loop between `count` executions provide just enough "air" for the JavaScript engine to do something else, to react on other user actions.
Now, if a new side task (e.g. `onclick` event) appears while the engine is busy executing part 1, it gets queued and then executes when part 1 finished, before the next part. Periodic returns to the event loop between `count` executions provide just enough "air" for the JavaScript engine to do something else, to react to other user actions.
The notable thing is that both variants -- with and without splitting the job by `setTimeout` -- are comparable in speed. There's no much difference in the overall counting time.
The notable thing is that both variants -- with and without splitting the job by `setTimeout` -- are comparable in speed. There's not much difference in the overall counting time.
To make them closer, let's make an improvement.
We'll move the scheduling in the beginning of the `count()`:
We'll move the scheduling to the beginning of the `count()`:
```js run
let i = 0;
@ -128,7 +128,7 @@ let start = Date.now();
function count() {
// move the scheduling at the beginning
// move the scheduling to the beginning
if (i < 1e9 - 1e6) {
setTimeout(count); // schedule the new call
}
@ -162,7 +162,7 @@ Another benefit of splitting heavy tasks for browser scripts is that we can show
Usually the browser renders after the currently running code is complete. Doesn't matter if the task takes a long time. Changes to DOM are painted only after the task is finished.
From one hand, that's great, because our function may create many elements, add them one-by-one to the document and change their styles -- the visitor won't see any "intermediate", unfinished state. An important thing, right?
On one hand, that's great, because our function may create many elements, add them one-by-one to the document and change their styles -- the visitor won't see any "intermediate", unfinished state. An important thing, right?
Here's the demo, the changes to `i` won't show up until the function finishes, so we'll see only the last value:
@ -263,11 +263,11 @@ What's going to be the order here?
2. `promise` shows second, because `.then` passes through the microtask queue, and runs after the current code.
3. `timeout` shows last, because it's a macrotask.
The richer event loop picture looks like this:
The richer event loop picture looks like this (order is from top to bottom, that is: the script first, then microtasks, rendering and so on):
![](eventLoop-full.svg)
**All microtasks are completed before any other event handling or rendering or any other macrotask takes place.**
All microtasks are completed before any other event handling or rendering or any other macrotask takes place.
That's important, as it guarantees that the application environment is basically the same (no mouse coordinate changes, no new network data, etc) between microtasks.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before After
Before After

View file

@ -268,7 +268,7 @@ If we're going to open a popup, a good practice is to inform the user about it.
- Browsers block `open` calls from the code outside of user actions. Usually a notification appears, so that a user may allow them.
- Browsers open a new tab by default, but if sizes are provided, then it'll be a popup window.
- The popup may access the opener window using the `window.opener` property.
- The main window and the popup can freely read and modify each other if they havee the same origin. Otherwise, they can change location of each other and [exchange messages.
- The main window and the popup can freely read and modify each other if they have the same origin. Otherwise, they can change location of each other and [exchange messages](info:cross-window-communication).
To close the popup: use `close()` call. Also the user may close them (just like any other windows). The `window.closed` is `true` after that.

View file

@ -58,7 +58,7 @@ alert( new TextDecoder().decode(binaryString) ); // Hello
The syntax is:
```js run
```js
let encoder = new TextEncoder();
```

View file

@ -1,7 +1,7 @@
# Fetch
JavaScript can send network requests to the server and load new information whenever is needed.
JavaScript can send network requests to the server and load new information whenever it's needed.
For example, we can use a network request to:
@ -66,7 +66,7 @@ if (response.ok) { // if HTTP-status is 200-299
- **`response.formData()`** -- return the response as `FormData` object (explained in the [next chapter](info:formdata)),
- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type),
- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representaion of binary data),
- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk, we'll see an example later.
- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows you to read the body chunk-by-chunk, we'll see an example later.
For instance, let's get a JSON-object with latest commits from GitHub:
@ -230,7 +230,7 @@ But, as we're going to send JSON, we use `headers` option to send `application/j
We can also submit binary data with `fetch` using `Blob` or `BufferSource` objects.
In this example, there's a `<canvas>` where we can draw by moving a mouse over it. A click on the "submit" button sends the image to server:
In this example, there's a `<canvas>` where we can draw by moving a mouse over it. A click on the "submit" button sends the image to the server:
```html run autorun height="90"
<body style="margin:0">

View file

@ -3,9 +3,9 @@
The built-in [URL](https://url.spec.whatwg.org/#api) class provides a convenient interface for creating and parsing URLs.
There are no networking methods that require exactly an `URL` object, strings are good enough. So technically we don't have to use `URL`. But sometimes it can be really helpful.
There are no networking methods that require exactly a `URL` object, strings are good enough. So technically we don't have to use `URL`. But sometimes it can be really helpful.
## Creating an URL
## Creating a URL
The syntax to create a new `URL` object:
@ -63,14 +63,14 @@ Here's the cheatsheet for URL components:
```smart header="We can pass `URL` objects to networking (and most other) methods instead of a string"
We can use an `URL` object in `fetch` or `XMLHttpRequest`, almost everywhere where an URL-string is expected.
We can use a `URL` object in `fetch` or `XMLHttpRequest`, almost everywhere where a URL-string is expected.
Generally, `URL` object can be passed to any method instead of a string, as most method will perform the string conversion, that turns an `URL` object into a string with full URL.
Generally, `URL` object can be passed to any method instead of a string, as most method will perform the string conversion, that turns a `URL` object into a string with full URL.
```
## SearchParams "?..."
Let's say we want to create an url with given search params, for instance, `https://google.com/search?query=JavaScript`.
Let's say we want to create a url with given search params, for instance, `https://google.com/search?query=JavaScript`.
We can provide them in the URL string:

View file

@ -19,7 +19,7 @@ Both storage objects provide same methods and properties:
- `key(index)` -- get the key on a given position.
- `length` -- the number of stored items.
As you can see, it's like a `Map` collection (`setItem/getItem/removeItem`), but also keeps elements order and allows to access by index with `key(index)`.
As you can see, it's like a `Map` collection (`setItem/getItem/removeItem`), but also allows to access by index with `key(index)`.
Let's see how it works.

View file

@ -153,9 +153,9 @@ Luckily, there's an alternative, that works everywhere. We can use a regexp like
alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (match!)
```
The pattern `pattern:[\s\S]` literally says: "a space character OR not a space character". In other words, "anything". We could use another pair of complementary classes, such as `pattern:[\d\D]`, that doesn't matter.
The pattern `pattern:[\s\S]` literally says: "a space character OR not a space character". In other words, "anything". We could use another pair of complementary classes, such as `pattern:[\d\D]`, that doesn't matter. Or even the `pattern:[^]` -- as it means match any character except nothing.
This trick works everywhere. Also we can use it if we don't want to set `pattern:s` flag, in cases when we want a regular "no-newline" dot too in the pattern.
Also we can use this trick if we want both kind of "dots" in the same pattern: the actual dot `pattern:.` behaving the regular way ("not including a newline"), and also a way to match "any character" with `pattern:[\s\S]` or alike.
````
````warn header="Pay attention to spaces"

View file

@ -26,7 +26,7 @@ More complex tests are possible, e.g. `pattern:X(?=Y)(?=Z)` means:
1. Find `pattern:X`.
2. Check if `pattern:Y` is immediately after `pattern:X` (skip if isn't).
3. Check if `pattern:Z` is immediately after `pattern:X` (skip if isn't).
3. Check if `pattern:Z` is immediately after `pattern:Y` (skip if isn't).
4. If both tests passed, then it's the match.
In other words, such pattern means that we're looking for `pattern:X` followed by `pattern:Y` and `pattern:Z` at the same time.

Binary file not shown.