diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index 35f44a41..e20bc2e0 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -314,7 +314,7 @@ if (str.indexOf("Widget") != -1) { One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation. -For 32-bit integers the call `~n` means exactly the same as `-(n+1)` (due to IEEE-754 format). +In practice, that means a simple thing: for 32-bit integers `~n` equals `-(n+1)`. For instance: @@ -345,7 +345,7 @@ It is usually not recommended to use language features in a non-obvious way, but Just remember: `if (~str.indexOf(...))` reads as "if found". -Technically speaking, numbers are truncated to 32 bits by `~` operator, so there exist other big numbers that give `0`, the smallest is `~4294967295=0`. That makes such check is correct only if a string is not that long. +To be precise though, as big numbers are truncated to 32 bits by `~` operator, there exist other numbers that give `0`, the smallest is `~4294967295=0`. That makes such check is correct only if a string is not that long. Right now we can see this trick only in the old code, as modern JavaScript provides `.includes` method (see below). @@ -519,7 +519,7 @@ alert( str ); // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` -See? Capital characters go first, then a few special ones, then lowercase characters. +See? Capital characters go first, then a few special ones, then lowercase characters, and `Ö` near the end of the output. Now it becomes obvious why `a > Z`. diff --git a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md index 8bb52150..18ba8439 100644 --- a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md +++ b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md @@ -35,4 +35,4 @@ alert(generator()); // 282475249 alert(generator()); // 1622650073 ``` -That's fine for this context. But then we loose ability to iterate with `for..of` and to use generator composition, that may be useful elsewhere. +That also works. But then we loose ability to iterate with `for..of` and to use generator composition, that may be useful elsewhere. diff --git a/1-js/12-generators-iterators/1-generators/article.md b/1-js/12-generators-iterators/1-generators/article.md index 531d5977..4927d6b2 100644 --- a/1-js/12-generators-iterators/1-generators/article.md +++ b/1-js/12-generators-iterators/1-generators/article.md @@ -19,14 +19,16 @@ function* generateSequence() { } ``` -When `generateSequence()` is called, it does not execute the code. Instead, it returns a special object, called "generator". +The term "generator function" is a bit misleading, because when called it does not execute the code. Instead, it returns a special object, called "generator object". + +So it's kind of a "generator constructor". ```js // "generator function" creates "generator object" let generator = generateSequence(); ``` -The `generator` object can be perceived as a "frozen function call": +The `generator` object is something like an "frozen function call": ![](generateSequence-1.png) @@ -60,7 +62,7 @@ As of now, we got the first value only: ![](generateSequence-2.png) -Let's call `generator.next()` again. It resumes the execution and returns the next `yield`: +Let's call `generator.next()` again. It resumes the code execution and returns the next `yield`: ```js let two = generator.next(); @@ -152,7 +154,7 @@ alert(sequence); // 0, 1, 2, 3 In the code above, `...generateSequence()` turns the iterable into array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator)) -## Using generators instead of iterables +## Using generators for iterables Some time ago, in the chapter [](info:iterable) we created an iterable `range` object that returns values `from..to`. @@ -163,7 +165,7 @@ let range = { from: 1, to: 5, - // for..of calls this method once in the very beginning + // for..of range calls this method once in the very beginning [Symbol.iterator]() { // ...it returns the iterator object: // onward, for..of works only with that object, asking it for next values @@ -187,7 +189,7 @@ let range = { alert([...range]); // 1,2,3,4,5 ``` -Using a generator to make iterable sequences is so much more elegant: +Using a generator to make iterable sequences is simpler and much more elegant: ```js run function* generateSequence(start, end) { @@ -201,11 +203,11 @@ let sequence = [...generateSequence(1,5)]; alert(sequence); // 1, 2, 3, 4, 5 ``` -...But what if we'd like to keep a custom `range` object? - ## Converting Symbol.iterator to generator -We can get the best from both worlds by providing a generator as `Symbol.iterator`: +We can add generator-style iteration to any custom object by providing a generator as `Symbol.iterator`. + +Here's the same `range`, but with a much more compact iterator: ```js run let range = { @@ -224,19 +226,18 @@ alert( [...range] ); // 1,2,3,4,5 The `range` object is now iterable. -That works pretty well, because when `range[Symbol.iterator]` is called: -- it returns an object (now a generator) -- that has `.next()` method (yep, a generator has it) -- that returns values in the form `{value: ..., done: true/false}` (check, exactly what generator does). +That works, because `range[Symbol.iterator]()` now returns a generator, and generator methods are exactly what `for..of` expects: +- it has `.next()` method +- that returns values in the form `{value: ..., done: true/false}` -That's not a coincidence, of course. Generators aim to make iterables easier, so we can see that. +That's not a coincidence, of course. Generators were added to JavaScript language with iterators in mind, to implement them easier. -The last variant with a generator is much more concise than the original iterable code, and keeps the same functionality. +The last variant with a generator is much more concise than the original iterable code of `range`, and keeps the same functionality. -```smart header="Generators may continue forever" +```smart header="Generators may generate values forever" In the examples above we generated finite sequences, but we can also make a generator that yields values forever. For instance, an unending sequence of pseudo-random numbers. -That surely would require a `break` in `for..of`, otherwise the loop would repeat forever and hang. +That surely would require a `break` (or `return`) in `for..of` over such generator, otherwise the loop would repeat forever and hang. ``` ## Generator composition @@ -248,7 +249,7 @@ For instance, we'd like to generate a sequence of: - followed by alphabet letters `a..z` (character codes 65..90) - followed by uppercased letters `A..Z` (character codes 97..122) -Then we plan to create passwords by selecting characters from it (could add syntax characters as well), but need to generate the sequence first. +We can use the sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let's generate it first. We already have `function* generateSequence(start, end)`. Let's reuse it to deliver 3 sequences one after another, together they are exactly what we need. @@ -285,7 +286,7 @@ for(let code of generatePasswordCodes()) { alert(str); // 0..9A..Za..z ``` -The special `yield*` directive in the example is responsible for the composition. It *delegates* the execution to another generator. Or, to say it simple, it runs generators and transparently forwards their yields outside, as if they were done by the calling generator itself. +The special `yield*` directive in the example is responsible for the composition. It *delegates* the execution to another generator. Or, to say it simple, `yield* gen` iterates over the generator `gen` and transparently forwards its yields outside. As if the values were yielded by the outer generator. The result is the same as if we inlined the code from nested generators: @@ -357,14 +358,16 @@ 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 to calculate the value. This is also a valid code: +Please note, the outer code does not have to immediately call`next(4)`. It may take time to calculate the value. That's not a problem: the generator will resume when the call is made. + +This is also a valid code: ```js // resume the generator after some time setTimeout(() => generator.next(4), 1000); ``` -The syntax may seem a bit odd. It's quite uncommon for a function and the calling code to pass values around to each other. But that's exactly what's going on. +As we can see, unlike regular functions, generators and the calling code can exchange results by passing values in `next/yield`. To make things more obvious, here's another example, with more calls: @@ -462,8 +465,8 @@ If we don't catch the error there, then, as usual, it falls through to the outer - Inside generators (only) there exists a `yield` operator. - The outer code and the generator may exchange results via `next/yield` calls. -In modern JavaScript, generators are rarely used. But sometimes they come in handy, because the ability of a function to exchange data with the calling code during the execution is quite unique. +In modern JavaScript, generators are rarely used. But sometimes they come in handy, because the ability of a function to exchange data with the calling code during the execution is quite unique. And, surely, they are great for making iterable objects. -Also, in the next chapter we'll learn async generators, which are used to read streams of asynchronously generated data in `for` loop. +Also, in the next chapter we'll learn async generators, which are used to read streams of asynchronously generated data (e.g paginated fetches over a network) in `for await ... of` loop. -In web-programming we often work with streamed data, e.g. need to fetch paginated results, so that's a very important use case. +In web-programming we often work with streamed data, so that's another very important use case. diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md index 0b8b3572..f890256c 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md @@ -1,11 +1,12 @@ - # Error on reading non-existant property -Create a proxy that throws an error for an attempt to read of a non-existant property. +Usually, an attempt to read a non-existant property returns `undefined`. + +Create a proxy that throws an error for an attempt to read of a non-existant property instead. That can help to detect programming mistakes early. -Write a function `wrap(target)` that takes an object `target` and return a proxy instead with that functionality. +Write a function `wrap(target)` that takes an object `target` and return a proxy that adds this functionality aspect. That's how it should work: diff --git a/1-js/99-js-misc/01-proxy/02-array-negative/task.md b/1-js/99-js-misc/01-proxy/02-array-negative/task.md index 6663b6f5..9b0b13f5 100644 --- a/1-js/99-js-misc/01-proxy/02-array-negative/task.md +++ b/1-js/99-js-misc/01-proxy/02-array-negative/task.md @@ -1,7 +1,7 @@ # Accessing array[-1] -In some languages, we can access array elements using negative indexes, counted from the end. +In some programming languages, we can access array elements using negative indexes, counted from the end. Like this: diff --git a/1-js/99-js-misc/01-proxy/03-observable/solution.md b/1-js/99-js-misc/01-proxy/03-observable/solution.md index dcb4f2a7..c0797a85 100644 --- a/1-js/99-js-misc/01-proxy/03-observable/solution.md +++ b/1-js/99-js-misc/01-proxy/03-observable/solution.md @@ -1,6 +1,6 @@ The solution consists of two parts: -1. Whenever `.observe(handler)` is called, we need to remember the handler somewhere, to be able to call it later. We can store it right in the object, using our symbol as the key. +1. Whenever `.observe(handler)` is called, we need to remember the handler somewhere, to be able to call it later. We can store handlers right in the object, using our symbol as the property key. 2. We need a proxy with `set` trap to call handlers in case of any change. ```js run diff --git a/1-js/99-js-misc/01-proxy/03-observable/task.md b/1-js/99-js-misc/01-proxy/03-observable/task.md index 6220104c..0e4b2761 100644 --- a/1-js/99-js-misc/01-proxy/03-observable/task.md +++ b/1-js/99-js-misc/01-proxy/03-observable/task.md @@ -20,10 +20,8 @@ user.observe((key, value) => { user.name = "John"; // alerts: SET name=John ``` -In other words, an object returned by `makeObservable` has the method `observe(handler)`. +In other words, an object returned by `makeObservable` has the method `observe(handler)` that allows to add `handler` function to be called on a property change. Whenever a property changes, `handler(key, value)` is called with the name and value o the property. - P.S. In this task, please handle only writing to a property. Other operations can be implemented in a similar way. -P.P.S. You might want to introduce a global variable or a global structure to store handlers. That's fine here. In real life, such function lives in a module, that has its own global scope. diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index 63b4ca2f..cf1182a4 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -91,11 +91,11 @@ The most common traps are for reading/writing properties. To intercept the reading, the `handler` should have a method `get(target, property, receiver)`. -It triggers when a property is read: +It triggers when a property is read, with following arguments: - `target` -- is the target object, the one passed as the first argument to `new Proxy`, - `property` -- property name, -- `receiver` -- if the property is a getter, then `receiver` is the object that's going to be used as `this` in that code. Usually that's the `proxy` object itself (or an object that inherits from it, if we inherit from proxy). +- `receiver` -- if the target property is a getter, then `receiver` is the object that's going to be used as `this` in its code. Usually that's the `proxy` object itself (or an object that inherits from it, if we inherit from proxy). Right now we don't need this argument, will be explained in more details letter. Let's use `get` to implement default values for an object. @@ -167,7 +167,7 @@ alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (no translation) */!* ``` -````smart header="Proxy should be used instead of `target` everywhere" +````smart Please note how the proxy overwrites the variable: ```js @@ -175,21 +175,21 @@ dictionary = new Proxy(dictionary, ...); numbers = new Proxy(numbers, ...); ``` -The proxy should totally replace the target object everywhere. No one should ever reference the target object after it got proxied. Otherwise it's easy to mess up. +The proxy should totally replace the target object everywhere. No one should ever reference the target object after it got proxied. Otherwise it 's easy to mess up. ```` ## Validation with "set" trap Now let's intercept writing as well. -Let's say we want a numeric array. If a value of another type is added, there should be an error. +Let's say we want a numbers-only array. If a value of another type is added, there should be an error. The `set` trap triggers when a property is written: `set(target, property, value, receiver)` - `target` -- is the target object, the one passed as the first argument to `new Proxy`, - `property` -- property name, - `value` -- property value, -- `receiver` -- same as in `get` trap, only matters if the property is a setter. +- `receiver` -- similar to `get` trap, matters only for setter properties. The `set` trap should return `true` if setting is successful, and `false` otherwise (leads to `TypeError`). @@ -238,7 +238,7 @@ If it returns a falsy value (or doesn't return anything), that triggers `TypeErr ## Protected properties with "deleteProperty" and "ownKeys" -There's a widespread convention that properties and methods prefixed by an underscore `_` are internal. They shouldn't be accessible from outside the object. +There's a widespread convention that properties and methods prefixed by an underscore `_` are internal. They shouldn't be accessed from outside the object. Technically, that's possible though: @@ -409,7 +409,7 @@ We can wrap a proxy around a function as well. The `apply(target, thisArg, args)` trap handles calling a proxy as function: -- `target` is the target object, +- `target` is the target object (function is an object in JavaScript), - `thisArg` is the value of `this`. - `args` is a list of arguments. @@ -454,17 +454,16 @@ function sayHi(user) { } *!* -alert(sayHi.length); // 1 (function length is the arguments count) +alert(sayHi.length); // 1 (function length is the arguments count in its declaration) */!* sayHi = delay(sayHi, 3000); *!* -alert(sayHi.length); // 0 (wrapper has no arguments) +alert(sayHi.length); // 0 (in the wrapper declaration, there are zero arguments) */!* ``` - `Proxy` is much more powerful, as it forwards everything to the target object. Let's use `Proxy` instead of a wrapping function: @@ -495,13 +494,13 @@ The result is the same, but now not only calls, but all operations on the proxy We've got a "richer" wrapper. -There exist other traps, but probably you've already got the idea. +There exist other traps: the full list is in the beginning of this chapter. Their usage pattern is similar to the above. ## Reflect The `Reflect` API was designed to work in tandem with `Proxy`. -For every internal object operation that can be trapped, there's a `Reflect` method. It has the same name and arguments as the trap, and can be used to forward the operation to an object. +For every internal object operation that can be trapped, there's a `Reflect` method. It has the same name and arguments as the trap, and can be used to forward the operation to an object from the trap. For example: @@ -529,14 +528,14 @@ let name = user.name; // GET name user.name = "Pete"; // SET name TO Pete ``` -- `Reflect.get` gets the property, like `target[prop]` that we used before. -- `Reflect.set` sets the property, like `target[prop] = value`, and also ensures the correct return value. +- `Reflect.get` gets the property, like `target[prop]`. +- `Reflect.set` sets the property, like `target[prop] = value`, and returns `true/false` as needed by `[[Set]]`. In most cases, we can do the same thing without `Reflect`. But we may miss some peculiar aspects. Consider the following example, it doesn't use `Reflect` and doesn't work right. -We have a proxied user object and inherit from it, then use a getter: +We have a proxied `user` object with `name` getter and inherit `admin` from it: ```js run let user = { @@ -632,7 +631,7 @@ Still, it's not perfect. There are limitations. Many built-in objects, for example `Map`, `Set`, `Date`, `Promise` and others make use of so-called "internal slots". -These are like properties, but reserved for internal purposes. Built-in methods access them directly, not via `[[Get]]/[[Set]]` internal methods. So `Proxy` can't intercept that. +These are like properties, but reserved for internal, specification-only purposes. Built-in methods access them directly, not via `[[Get]]/[[Set]]` internal methods. So `Proxy` can't intercept that. Who cares? They are internal anyway! @@ -779,7 +778,7 @@ A *revocable* proxy is a proxy that can be disabled. Let's say we have a resource, and would like to close access to it any moment. -What we can do is to wrap it into a revocable proxy, without any traps. Such proxy will forward operations to object, and we also get a special method to disable it. +What we can do is to wrap it into a revocable proxy, without any traps. Such proxy will forward operations to object, and we can disable it at any moment. The syntax is: @@ -810,8 +809,7 @@ alert(proxy.data); // Error A call to `revoke()` removes all internal references to the target object from the proxy, so they are no more connected. The target object can be garbage-collected after that. -We can also store `revoke` in a `WeakMap`, to be able to easily find it by the proxy: - +We can also store `revoke` in a `WeakMap`, to be able to easily find it by a proxy object: ```js run *!* @@ -835,7 +833,7 @@ alert(proxy.data); // Error (revoked) The benefit of such approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needeed. -Using `WeakMap` instead of `Map` here, because it should not block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory (we don't need its revoke in that case). +Using `WeakMap` instead of `Map` here, because it should not block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more. ## References diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index 1de548a2..e9494b42 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -22,7 +22,7 @@ function sayHi() { alert("Hello"); } -// global functions are accessible as properties of window +// global functions are methods of the global objecct: window.sayHi(); ``` diff --git a/2-ui/2-events/05-dispatch-events/article.md b/2-ui/2-events/05-dispatch-events/article.md index e1bea156..dbe62302 100644 --- a/2-ui/2-events/05-dispatch-events/article.md +++ b/2-ui/2-events/05-dispatch-events/article.md @@ -8,17 +8,17 @@ Also we can generate built-in events like `click`, `mousedown` etc, that may be ## Event constructor -Events form a hierarchy, just like DOM element classes. The root is the built-in [Event](http://www.w3.org/TR/dom/#event) class. +Build-in event classes form a hierarchy, similar to DOM element classes. The root is the built-in [Event](http://www.w3.org/TR/dom/#event) class. We can create `Event` objects like this: ```js -let event = new Event(event type[, options]); +let event = new Event(type[, options]); ``` Arguments: -- *event type* -- may be any string, like `"click"` or our own like `"hey-ho!"`. +- *type* -- event type, a string like `"click"` or our own like `"my-event"`. - *options* -- the object with two optional properties: - `bubbles: true/false` -- if `true`, then the event bubbles. - `cancelable: true/false` -- if `true`, then the "default action" may be prevented. Later we'll see what it means for custom events. @@ -66,9 +66,13 @@ All we need is to set `bubbles` to `true`: // ...dispatch on elem! let event = new Event("hello", {bubbles: true}); // (2) elem.dispatchEvent(event); + + // the handler on document will activate and display the message. + ``` + Notes: 1. We should use `addEventListener` for our custom events, because `on` only exists for built-in events, `document.onhello` doesn't work. @@ -160,11 +164,9 @@ The event class tells something about "what kind of event" it is, and if the eve We can call `event.preventDefault()` on a script-generated event if `cancelable:true` flag is specified. -Of course, if the event has a non-standard name, then it's not known to the browser, and there's no "default browser action" for it. +Of course, for custom events, with names unknown for the browser, there are no "default browser actions". But our code may plan its own actions after `dispatchEvent`. -But the event-generating code may plan some actions after `dispatchEvent`. - -The call of `event.preventDefault()` is a way for the handler to send a signal that those actions shouldn't be performed. +The call of `event.preventDefault()` is a way for the handler to send a signal that those actions should be canceled . In that case the call to `elem.dispatchEvent(event)` returns `false`. And the event-generating code knows that the processing shouldn't continue. @@ -267,7 +269,7 @@ Now `dispatchEvent` runs asynchronously after the current code execution is fini ## Summary -To generate an event, we first need to create an event object. +To generate an event from code, we first need to create an event object. The generic `Event(name, options)` constructor accepts an arbitrary event name and the `options` object with two properties: - `bubbles: true` if the event should bubble. diff --git a/2-ui/3-event-details/1-mouse-events-basics/article.md b/2-ui/3-event-details/1-mouse-events-basics/article.md index dce1ad3d..ccc06278 100644 --- a/2-ui/3-event-details/1-mouse-events-basics/article.md +++ b/2-ui/3-event-details/1-mouse-events-basics/article.md @@ -113,7 +113,7 @@ For JS-code it means that we should check `if (event.ctrlKey || event.metaKey)`. ```warn header="There are also mobile devices" Keyboard combinations are good as an addition to the workflow. So that if the visitor has a - keyboard -- it works. And if your device doesn't have it -- then there's another way to do the same. + keyboard -- it works. And if their device doesn't have it -- then there should be another way to do the same. ``` ## Coordinates: clientX/Y, pageX/Y @@ -123,7 +123,7 @@ All mouse events have coordinates in two flavours: 1. Window-relative: `clientX` and `clientY`. 2. Document-relative: `pageX` and `pageY`. -For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then `clientX` and `clientY` are `0`. And if the mouse is in the center, then `clientX` and `clientY` are `250`, no matter what place in the document it is. They are similar to `position:fixed`. +For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then `clientX` and `clientY` are `0`. And if the mouse is in the center, then `clientX` and `clientY` are `250`, no matter what place in the document it is, how far the document was scrolled. They are similar to `position:fixed`. ````online Move the mouse over the input field to see `clientX/clientY` (it's in the `iframe`, so coordinates are relative to that `iframe`): @@ -138,9 +138,9 @@ Coordinates `pageX`, `pageY` are similar to `position:absolute` on the document You can read more about coordinates in the chapter . -## No selection on mousedown +## Disabling selection on mousedown -Mouse clicks have a side-effect that may be disturbing. A double click selects the text. +Mouse clicks have a side-effect that may be disturbing in some interfaces: a double click selects the text. If we want to handle click events ourselves, then the "extra" selection doesn't look good. @@ -191,7 +191,7 @@ Before... Now the bold element is not selected on double clicks. -The text inside it is still selectable. However, the selection should start not on the text itself, but before or after it. Usually that's fine though. +The text inside it is still selectable. However, the selection should start not on the text itself, but before or after it. Usually that's fine for users. ````smart header="Canceling the selection" Instead of *preventing* the selection, we can cancel it "post-factum" in the event handler. @@ -235,9 +235,11 @@ Mouse events have the following properties: - Window-relative coordinates: `clientX/clientY`. - Document-relative coordinates: `pageX/pageY`. -It's also important to deal with text selection as an unwanted side-effect of clicks. +It's also important to deal with text selection, it may be an unwanted side-effect of clicks. There are several ways to do this, for instance: 1. The CSS-property `user-select:none` (with browser prefixes) completely disables text-selection. 2. Cancel the selection post-factum using `getSelection().removeAllRanges()`. 3. Handle `mousedown` and prevent the default action (usually the best). + +The selection is a separate topic, covered in another chapter . diff --git a/5-network/10-long-polling/article.md b/5-network/10-long-polling/article.md index 9fc59e08..19624e09 100644 --- a/5-network/10-long-polling/article.md +++ b/5-network/10-long-polling/article.md @@ -13,16 +13,14 @@ That is, periodical requests to the server: "Hello, I'm here, do you have any in In response, the server first takes a notice to itself that the client is online, and second - sends a packet of messages it got till that moment. That works, but there are downsides: -1. Messages are passed with a delay up to 10 seconds. +1. Messages are passed with a delay up to 10 seconds (between requests). 2. Even if there are no messages, the server is bombed with requests every 10 seconds. That's quite a load to handle for backend, speaking performance-wise. -So, if we're talking about a very small service, the approach may be viable. - -But generally, it needs an improvement. +So, if we're talking about a very small service, the approach may be viable, but generally, it needs an improvement. ## Long polling -Long polling -- is a better way to poll the server. +So-called "long polling" is a much better way to poll the server. It's also very easy to implement, and delivers messages without delays. @@ -37,16 +35,17 @@ The situation when the browser sent a request and has a pending connection with ![](long-polling.png) -Even if the connection is lost, because of, say, a network error, the browser immediately sends a new request. +If the connection is lost, because of, say, a network error, the browser immediately sends a new request. -A sketch of client-side code: +A sketch of client-side `subscribe` function that makes long requests: ```js async function subscribe() { let response = await fetch("/subscribe"); if (response.status == 502) { - // Connection timeout, happens when the connection was pending for too long + // Connection timeout error, + // may happen when the connection was pending for too long, and the remote server or a proxy closed it // let's reconnect await subscribe(); } else if (response.status != 200) { @@ -66,7 +65,7 @@ async function subscribe() { subscribe(); ``` -The `subscribe()` function makes a fetch, then waits for the response, handles it and calls itself again. +As you can see, `subscribe` function makes a fetch, then waits for the response, handles it and calls itself again. ```warn header="Server should be ok with many pending connections" The server architecture must be able to work with many pending connections. diff --git a/5-network/10-long-polling/longpoll.view/index.html b/5-network/10-long-polling/longpoll.view/index.html index 81b62738..7452c183 100644 --- a/5-network/10-long-polling/longpoll.view/index.html +++ b/5-network/10-long-polling/longpoll.view/index.html @@ -13,6 +13,6 @@ All visitors of this page will see messages of each other. diff --git a/5-network/10-long-polling/longpoll.view/server.js b/5-network/10-long-polling/longpoll.view/server.js index ab30a816..c3903e37 100644 --- a/5-network/10-long-polling/longpoll.view/server.js +++ b/5-network/10-long-polling/longpoll.view/server.js @@ -75,7 +75,7 @@ if (!module.parent) { } else { exports.accept = accept; - if (process.send) { // if run by pm2 have this defined + if (process.send) { process.on('message', (msg) => { if (msg === 'shutdown') { close(); diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index aeb2ca78..7e71db32 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -41,7 +41,7 @@ data: of two lines - Messages are delimited with double line breaks `\n\n`. - To send a line break `\n`, we can immediately one more `data:` (3rd message above). -In practice, complex messages are usually sent JSON-encoded, so line-breaks are encoded within them. +In practice, complex messages are usually sent JSON-encoded. Line-breaks are encoded as `\n` within them, so multiline `data:` messages are not necessary. For instance: @@ -102,7 +102,7 @@ data: Hello, I set the reconnection delay to 15 seconds The `retry:` may come both together with some data, or as a standalone message. -The browser should wait that much before reconnect. If the network connection is lost, the browser may wait till it's restored, and then retry. +The browser should wait that many milliseconds before reconnect. Or longer, e.g. if the browser knows (from OS) that there's no network connection at the moment, it may wait until the connection appears, and then retry. - If the server wants the browser to stop reconnecting, it should respond with HTTP status 204. - If the browser wants to close the connection, it should call `eventSource.close()`: @@ -116,7 +116,7 @@ eventSource.close(); Also, there will be no reconnection if the response has an incorrect `Content-Type` or its HTTP status differs from 301, 307, 200 and 204. The connection the `"error"` event is emitted, and the browser won't reconnect. ```smart -There's no way to "reopen" a closed connection. If we'd like to connect again, just create a new `EventSource`. +When a connection is finally closed, there's no way to "reopen" it. If we'd like to connect again, just create a new `EventSource`. ``` ## Message id @@ -143,7 +143,7 @@ When a message with `id:` is received, the browser: - Upon reconnection sends the header `Last-Event-ID` with that `id`, so that the server may re-send following messages. ```smart header="Put `id:` after `data:`" -Please note: the `id:` is appended below the message data, to ensure that `lastEventId` is updated after the message data is received. +Please note: the `id` is appended below message `data` by the server, to ensure that `lastEventId` is updated after the message is received. ``` ## Connection status: readyState @@ -206,17 +206,16 @@ Then the browser automatically reconnects. [codetabs src="eventsource"] - ## Summary -The `EventSource` object communicates with the server. It establishes a persistent connection and allows the server to send messages over it. +`EventSource` object automatically establishes a persistent connection and allows the server to send messages over it. It offers: - Automatic reconnect, with tunable `retry` timeout. -- Message ids to resume events, the last identifier is sent in `Last-Event-ID` header. +- 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 these features. +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). In many real-life applications, the power of `EventSource` is just enough. @@ -262,9 +261,11 @@ The server may set a custom event name in `event:`. Such events should be handle The server sends messages, delimited by `\n\n`. -Message parts may start with: +A message may have following fields: - `data:` -- message body, a sequence of multiple `data` is interpreted as a single message, with `\n` between the parts. - `id:` -- renews `lastEventId`, sent in `Last-Event-ID` on reconnect. - `retry:` -- recommends a retry delay for reconnections in ms. There's no way to set it from JavaScript. - `event:` -- even name, must precede `data:`. + +A message may include one or more fields in any order, but `id:` usually goes the last. diff --git a/6-data-storage/02-localstorage/article.md b/6-data-storage/02-localstorage/article.md index 2e24affb..9b2279e1 100644 --- a/6-data-storage/02-localstorage/article.md +++ b/6-data-storage/02-localstorage/article.md @@ -61,7 +61,7 @@ alert( localStorage.test ); // 2 delete localStorage.test; ``` -That's allowed for historical reasons, and mostly works, but generally not recommended for two reasons: +That's allowed for historical reasons, and mostly works, but generally not recommended, because: 1. If the key is user-generated, it can be anything, like `length` or `toString`, or another built-in method of `localStorage`. In that case `getItem/setItem` work fine, while object-like access fails: ```js run @@ -157,7 +157,7 @@ Properties and methods are the same, but it's much more limited: - The `sessionStorage` exists only within the current browser tab. - Another tab with the same page will have a different storage. - - But it is shared between iframes in the tab (assuming they come from the same origin). + - But it is shared between iframes in the same tab (assuming they come from the same origin). - The data survives page refresh, but not closing/opening the tab. Let's see that in action. @@ -212,7 +212,7 @@ localStorage.setItem('now', Date.now()); Please note that the event also contains: `event.url` -- the url of the document where the data was updated. -Also, `event.storageArea` contains the storage object -- the event is the same for both `sessionStorage` and `localStorage`, so `storageArea` references the one that was modified. We may even want to set something back in it, to "respond" to a change. +Also, `event.storageArea` contains the storage object -- the event is the same for both `sessionStorage` and `localStorage`, so `event.storageArea` references the one that was modified. We may even want to set something back in it, to "respond" to a change. **That allows different windows from the same origin to exchange messages.** @@ -245,5 +245,5 @@ API: Storage event: - Triggers on `setItem`, `removeItem`, `clear` calls. -- Contains all the data about the operation, the document `url` and the storage object. +- Contains all the data about the operation (`key/oldValue/newValue`), the document `url` and the storage object `storageArea`. - Triggers on all `window` objects that have access to the storage except the one that generated it (within a tab for `sessionStorage`, globally for `localStorage`).