Merge branch 'master' into patch-1

This commit is contained in:
Ilya Kantor 2020-12-05 19:41:19 +03:00 committed by GitHub
commit 0db83b2858
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 82 additions and 53 deletions

View file

@ -6,7 +6,7 @@ That's simple:
let ourPlanetName = "Earth";
```
Note, we could use a shorter name `planet`, but it might be not obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong.
Note, we could use a shorter name `planet`, but it might not be obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong.
## The name of the current visitor

View file

@ -57,7 +57,7 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6
alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100
```
The solution has a time complexety of [O(n<sup>2</sup>)](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer.
The solution has a time complexity of [O(n<sup>2</sup>)](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer.
For big arrays (1000, 10000 or more items) such algorithms can lead to a serious sluggishness.

View file

@ -209,7 +209,7 @@ arr.push("Pear"); // modify the array by reference
alert( fruits ); // Banana, Pear - 2 items now
```
...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast.
...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast.
But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object.

View file

@ -207,7 +207,7 @@ alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!)
For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more.
If we replace `Map` with `WeakMap`, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected .
If we replace `Map` with `WeakMap`, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected.
```js run
// 📁 cache.js
@ -284,7 +284,8 @@ The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterati
`WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means.
It's main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector.
That comes at the cost of not having support for `clear`, `size`, `keys`, `values` ...
Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage colector.
That comes at the cost of not having support for `clear`, `size`, `keys`, `values`...
`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "primary" object storage. Once the object is removed from the primary storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically.

View file

@ -219,7 +219,7 @@ The first promise here was fastest, so it became the result. After the first set
## Promise.any
Similar to `Promise.race`, but waits only for the first fulfilled promise and gets its result. If all of the given promises are rejected, then the returned promise is rejected.
Similar to `Promise.race`, but waits only for the first fulfilled promise and gets its result. If all of the given promises are rejected, then the returned promise is rejected with [`AggregateError`](mdn:js/AggregateError) - a special error object that stores all promise errors in its `errors` property.
The syntax is:
@ -239,6 +239,20 @@ Promise.any([
The first promise here was fastest, but it was rejected, so the second promise became the result. After the first fulfilled promise "wins the race", all further results are ignored.
Here's an example when all promises fail:
```js run
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000))
]).catch(error => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors[0]); // Error: Ouch!
console.log(error.errors[1]); // Error: Error
});
```
As you can see, error objects for failed promises are available in the `errors` property of the `AggregateError` object.
## Promise.resolve/reject
@ -302,7 +316,7 @@ There are 5 static methods of `Promise` class:
- `status`: `"fulfilled"` or `"rejected"`
- `value` (if fulfilled) or `reason` (if rejected).
3. `Promise.race(promises)` -- waits for the first promise to settle, and its result/error becomes the outcome.
4. `Promise.any(promises)` -- waits for the first promise to fulfill, and its result becomes the outcome. If all of the given promises rejects, it becomes the error of `Promise.any`.
4. `Promise.any(promises)` -- waits for the first promise to fulfill, and its result becomes the outcome. If all of the given promises are rejected, [`AggregateError`](mdn:js/AggregateError) becomes the error of `Promise.any`.
5. `Promise.resolve(value)` -- makes a resolved promise with the given value.
6. `Promise.reject(error)` -- makes a rejected promise with the given error.

View file

@ -969,7 +969,7 @@ Initially, `revoke` is separate from `proxy`, so that we can pass `proxy` around
We can also bind `revoke` method to proxy by setting `proxy.revoke = revoke`.
Another option is to create a `WeakMap` that has `proxy` as the key the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy:
Another option is to create a `WeakMap` that has `proxy` as the key and the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy:
```js run
*!*

View file

@ -39,15 +39,19 @@ The clock-managing functions:
```js
let timerId;
function clockStart() { // run the clock
timerId = setInterval(update, 1000);
function clockStart() { // run the clock
if (!timerId) { // only set a new interval if the clock is not running
timerId = setInterval(update, 1000);
}
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
timerId = null;
timerId = null; // (**)
}
```
Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then.
Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`.

View file

@ -43,12 +43,17 @@
}
function clockStart() {
timerId = setInterval(update, 1000);
// set a new interval only if the clock is stopped
// otherwise we would rewrite the timerID reference to the running interval and wouldn't be able to stop the clock ever again
if (!timerId) {
timerId = setInterval(update, 1000);
}
update(); // <-- start right now, don't wait 1 second till the first setInterval works
}
function clockStop() {
clearInterval(timerId);
timerId = null; // <-- clear timerID to indicate that the clock has been stopped, so that it is possible to start it again in clockStart()
}
clockStart();

View file

@ -8,19 +8,17 @@ libs:
In this chapter we'll cover selection in the document, as well as selection in form fields, such as `<input>`.
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.
JavaScript can access an existing selection, select/deselect DOM nodes as a whole or partially, remove the selected content 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.
You can find some recipes for common tasks at the end of the chapter, in "Summary" section. Maybe that covers your current needs, but you'll get much more if you read the whole text.
The underlying `Range` and `Selection` objects are easy to grasp, and then you'll need no recipes to make them do what you want.
## Range
The basic concept of selection is [Range](https://dom.spec.whatwg.org/#ranges): basically, a pair of "boundary points": range start and range end.
The basic concept of selection is [Range](https://dom.spec.whatwg.org/#ranges), that is essentially 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 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.
First, we can create a range (the constructor has no parameters):
A `Range` object is created without parameters:
```js
let range = new Range();
@ -28,13 +26,18 @@ let range = new Range();
Then we can set the selection boundaries using `range.setStart(node, offset)` and `range.setEnd(node, offset)`.
For example, consider this fragment of HTML:
The first argument `node` can be either a text node or an element node. The meaning of the second argument depends on that:
```html
- If `node` is a text node, then `offset` must be the position in the text.
- If `node` is an element node, then `offset` must be the child number.
For example, let's create a range in this fragment:
```html autorun
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
```
Here's its DOM structure, note that here text nodes are important for us:
Here's its DOM structure:
<div class="select-p-domtree"></div>
@ -72,10 +75,17 @@ let selectPDomtree = {
drawHtmlTree(selectPDomtree, 'div.select-p-domtree', 690, 320);
</script>
Let's select `"Example: <i>italic</i>"`. That's two first children of `<p>` (counting text nodes):
Let's make a range for `"Example: <i>italic</i>"`.
As we can see, this phrase consists of exactly the first and the second children of `<p>`:
![](range-example-p-0-1.svg)
- The starting point has `<p>` as the parent `node`, and `0` as the offset.
- The ending point also has `<p>` as the parent `node`, but `2` as the offset (it specifies the range up to, but not including `offset`).
Here's the demo, if you run it, you can see that the text gets selected:
```html run
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
@ -87,17 +97,14 @@ Let's select `"Example: <i>italic</i>"`. That's two first children of `<p>` (cou
range.setEnd(p, 2);
*/!*
// toString of a range returns its content as text (without tags)
alert(range); // Example: italic
// toString of a range returns its content as text, without tags
console.log(range); // Example: italic
// apply this range for document selection (explained later)
// let's apply this range for document selection (explained later)
document.getSelection().addRange(range);
</script>
```
- `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:
```html run autorun
@ -121,7 +128,7 @@ From <input id="start" type="number" value=1> To <input id="end" type="numbe
</script>
```
E.g. selecting from `1` to `4` gives range `<i>italic</i> and <b>bold</b>`.
E.g. selecting in the same `<p>` from offset `1` to `4` gives range `<i>italic</i> and <b>bold</b>`:
![](range-example-p-1-3.svg)
@ -264,11 +271,9 @@ There also exist methods to compare ranges, but these are rarely used. When you
## Selection
`Range` is a generic object for managing selection ranges. We may create such objects, pass them around -- they do not visually select anything on their own.
`Range` is a generic object for managing selection ranges. We may create `Range` objects, pass them around -- they do not visually select anything on their own.
The document selection is represented by `Selection` object, that can be obtained as `window.getSelection()` or `document.getSelection()`.
A selection may include zero or more ranges. At least, the [Selection API specification](https://www.w3.org/TR/selection-api/) says so. In practice though, only Firefox allows to select multiple ranges in the document by using `key:Ctrl+click` (`key:Cmd+click` for Mac).
The document selection is represented by `Selection` object, that can be obtained as `window.getSelection()` or `document.getSelection()`. A selection may include zero or more ranges. At least, the [Selection API specification](https://www.w3.org/TR/selection-api/) says so. In practice though, only Firefox allows to select multiple ranges in the document by using `key:Ctrl+click` (`key:Cmd+click` for Mac).
Here's a screenshot of a selection with 3 ranges, made in Firefox:
@ -289,7 +294,7 @@ The main selection properties are:
- `isCollapsed` -- `true` if selection selects nothing (empty range), or doesn't exist.
- `rangeCount` -- count of ranges in the selection, maximum `1` in all browsers except Firefox.
````smart header="Selection end may be in the document before start"
````smart header="Usually, the selection end `focusNode` is after its start `anchorNode`, but it's not always the case"
There are many ways to select the content, depending on the user agent: mouse, hotkeys, taps on a mobile etc.
Some of them, such as a mouse, allow the same selection can be created in two directions: "left-to-right" and "right-to-left".

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

View file

@ -66,7 +66,7 @@ eventSource.onmessage = function(event) {
### Cross-origin requests
`EventSource` supports cross-origin requests, like `fetch` any other networking methods. We can use any URL:
`EventSource` supports cross-origin requests, like `fetch` and any other networking methods. We can use any URL:
```js
let source = new EventSource("https://another-site.com/events");
@ -215,7 +215,7 @@ It offers:
- Message ids to resume events, the last received identifier is sent in `Last-Event-ID` header upon reconnection.
- The current state is in the `readyState` property.
That makes `EventSource` a viable alternative to `WebSocket`, as it's more low-level and lacks such built-in features (though they can be implemented).
That makes `EventSource` a viable alternative to `WebSocket`, as the latter is more low-level and lacks such built-in features (though they can be implemented).
In many real-life applications, the power of `EventSource` is just enough.

View file

@ -7,7 +7,7 @@ Cookies are usually set by a web-server using response `Set-Cookie` HTTP-header.
One of the most widespread use cases is authentication:
1. Upon sign in, the server uses `Set-Cookie` HTTP-header in the response to set a cookie with a unique "session identifier".
2. Next time when the request is set to the same domain, the browser sends the cookie over the net using `Cookie` HTTP-header.
2. Next time when the request is sent to the same domain, the browser sends the cookie over the net using `Cookie` HTTP-header.
3. So the server knows who made the request.
We can also access cookies from the browser, using `document.cookie` property.
@ -96,7 +96,7 @@ Usually, we should set `path` to the root: `path=/` to make the cookie accessibl
A domain where the cookie is accessible. In practice though, there are limitations. We can't set any domain.
By default, a cookie is accessible only at the domain that set it. So, if the cookie was set by `site.com`, we won't get it `other.com`.
By default, a cookie is accessible only at the domain that set it. So, if the cookie was set by `site.com`, we won't get it at `other.com`.
...But what's more tricky, we also won't get the cookie at a subdomain `forum.site.com`!
@ -404,7 +404,7 @@ Websites generally have two variants of following GDPR. You must have seen them
2. If a website wants to set tracking cookies for everyone.
To do so legally, a website shows a modal "splash screen" for newcomers, and require them to agree for cookies. Then the website can set them and let people see the content. That can be disturbing for new visitors though. No one likes to see "must-click" modal splash screens instead of the content. But GDPR requires an explicit agreement.
To do so legally, a website shows a modal "splash screen" for newcomers, and requires them to agree for cookies. Then the website can set them and let people see the content. That can be disturbing for new visitors though. No one likes to see "must-click" modal splash screens instead of the content. But GDPR requires an explicit agreement.
GDPR is not only about cookies, it's about other privacy-related issues too, but that's too much beyond our scope.

View file

@ -202,7 +202,7 @@ If both windows are listening for `window.onstorage`, then each one will react o
```js run
// triggers on updates made to the same storage from other documents
window.onstorage = event => { // same as window.addEventListener('storage', () => {
window.onstorage = event => { // same as window.addEventListener('storage', event => {
if (event.key != 'now') return;
alert(event.key + ':' + event.newValue + " at " + event.url);
};

View file

@ -31,7 +31,7 @@ For two points we have a linear curve (that's a straight line), for three points
![](bezier4-e.svg) ![](bezier3-e.svg)
Because of that last property, in computer graphics it's possible to optimize intersection tests. If convex hulls do not intersect, then curves do not either. So checking for the convex hulls intersection first can give a very fast "no intersection" result. Checking the intersection or convex hulls is much easier, because they are rectangles, triangles and so on (see the picture above), much simpler figures than the curve.
Because of that last property, in computer graphics it's possible to optimize intersection tests. If convex hulls do not intersect, then curves do not either. So checking for the convex hulls intersection first can give a very fast "no intersection" result. Checking the intersection of convex hulls is much easier, because they are rectangles, triangles and so on (see the picture above), much simpler figures than the curve.
**The main value of Bezier curves for drawing -- by moving the points the curve is changing *in intuitively obvious way*.**

View file

@ -285,7 +285,7 @@ The first argument of `steps(9, start)` is the number of steps. The transform wi
The second argument is one of two words: `start` or `end`.
The `start` means that in the beginning of animation we need to do make the first step immediately.
The `start` means that in the beginning of animation we need to make the first step immediately.
We can observe that during the animation: when we click on the digit it changes to `1` (the first step) immediately, and then changes in the beginning of the next second.

View file

@ -2,7 +2,7 @@ To bounce we can use CSS property `top` and `position:absolute` for the ball ins
The bottom coordinate of the field is `field.clientHeight`. The CSS `top` property refers to the upper edge of the ball. So it should go from `0` till `field.clientHeight - ball.clientHeight`, that's the final lowest position of the upper edge of the ball.
To to get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode.
To get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode.
Here's the final code for the animation:

View file

@ -77,7 +77,7 @@ setInterval(animate3, 20);
These several independent redraws should be grouped together, to make the redraw easier for the browser and hence load less CPU load and look smoother.
There's one more thing to keep in mind. Sometimes when CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`.
There's one more thing to keep in mind. Sometimes CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`.
But how do we know about that in JavaScript? There's a specification [Animation timing](http://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more.
@ -227,7 +227,7 @@ See in action (click to activate):
[iframe height=40 src="quad" link]
...Or the cubic curve or event greater `n`. Increasing the power makes it speed up faster.
...Or the cubic curve or even greater `n`. Increasing the power makes it speed up faster.
Here's the graph for `progress` in the power `5`:
@ -397,7 +397,7 @@ The effect is clearly seen if we compare the graphs of `easeIn`, `easeOut` and `
![](circ-ease.svg)
- <span style="color:#EE6B47">Red</span> is the regular variantof `circ` (`easeIn`).
- <span style="color:#EE6B47">Red</span> is the regular variant of `circ` (`easeIn`).
- <span style="color:#8DB173">Green</span> -- `easeOut`.
- <span style="color:#62C0DC">Blue</span> -- `easeInOut`.
@ -405,7 +405,7 @@ As we can see, the graph of the first half of the animation is the scaled down `
## More interesting "draw"
Instead of moving the element we can do something else. All we need is to write the write the proper `draw`.
Instead of moving the element we can do something else. All we need is to write the proper `draw`.
Here's the animated "bouncing" text typing:

View file

@ -41,13 +41,13 @@ We can search for characters with a property, written as `pattern:\p{…}`. To u
For instance, `\p{Letter}` denotes a letter in any of language. We can also use `\p{L}`, as `L` is an alias of `Letter`. There are shorter aliases for almost every property.
In the example below three kinds of letters will be found: English, Georgean and Korean.
In the example below three kinds of letters will be found: English, Georgian and Korean.
```js run
let str = "A ბ ㄱ";
alert( str.match(/\p{L}/gu) ); // A,ბ,ㄱ
alert( str.match(/\p{L}/g) ); // null (no matches, as there's no flag "u")
alert( str.match(/\p{L}/g) ); // null (no matches, \p doesn't work without the flag "u")
```
Here's the main character categories and their subcategories:

Binary file not shown.