diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index 591a4c831..1addd6562 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -107,10 +107,18 @@ Moderne Werkzeuge machen die Transpilation sehr schnell und transparent und erla Beispiele für solche Sprachen sind: +<<<<<<< HEAD - [CoffeeScript](http://coffeescript.org/) ist ein "syntactic sugar" für JavaScript. Es führt eine kürzere Syntax ein, was uns erlaubt, klareren und präziseren Code zu schreiben. Usually, Ruby devs like it. - [TypeScript](http://www.typescriptlang.org/) ist darauf konzentriert "strict data typing" hinzuzufügen. TypeScript verfolg das Ziel den Entwicklungsprozess und den Support für komplexe Systeme zu vereinfachen. Die Sprache wurde von Microsoft entwickelt. - [Flow](http://flow.org/) fügt auch "data typing" hinzu, aber auf eine andere Art und Weise. Sie wurde von Facebook entwickelt. - [Dart](https://www.dartlang.org/) ist eine eigenständige Sprache, die eine eigene Engine hat, die in Nicht-Browser-Umgebungen (wie z.B. mobilen Anwendungen) läuft, aber auch in JavaScript umgesetzt werden kann. Sie wurde von Google entwickelt. +======= +- [CoffeeScript](http://coffeescript.org/) is a "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it. +- [TypeScript](http://www.typescriptlang.org/) is concentrated on adding "strict data typing" to simplify the development and support of complex systems. It is developed by Microsoft. +- [Flow](http://flow.org/) also adds data typing, but in a different way. Developed by Facebook. +- [Dart](https://www.dartlang.org/) is a standalone language that has its own engine that runs in non-browser environments (like mobile apps), but also can be transpiled to JavaScript. Developed by Google. +- [Brython](https://brython.info/) is a Python transpiler to JavaScript that allow to write application in pure Python without JavaScript. +>>>>>>> 58f6599df71b8d50417bb0a52b1ebdc995614017 Es gibt noch mehr. Auch wenn wir eine der transpilierten Sprachen verwenden sollten wir auch JavaScript trozdem kennen. Es ist wichtig zu verstehen, was im Hintergrund passiert und was wir eigentlich tun. diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 2c36d8c06..3ad266033 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -66,7 +66,11 @@ Mehr über das Arbeiten mit Zahlen erfahren wir in diesem Kapitel . ## BigInt +<<<<<<< HEAD In JavaScript kann der Typ "Zahl" keine ganzzahligen Werte darstellen, die größer als (253-1) (das ist `9007199254740991`) oder kleiner als -(-253-1) für Negative sind. Es handelt sich um eine technische Einschränkung, die durch ihre interne Darstellung bedingt ist. +======= +In JavaScript, the "number" type cannot represent integer values larger than (253-1) (that's `9007199254740991`), or less than -(253-1) for negatives. It's a technical limitation caused by their internal representation. +>>>>>>> 58f6599df71b8d50417bb0a52b1ebdc995614017 Für die meisten Zwecke reicht das völlig aus, aber manchmal brauchen wir wirklich große Zahlen, z.B. für die Kryptographie oder Zeitstempel mit Mikrosekunden-Genauigkeit. @@ -81,8 +85,13 @@ const bigInt = 1234567890123456789012345678901234567890n; Da `BigInt`-Zahlen selten benötigt werden, behandeln wir sie hier nicht, sondern widmen ihnen ein eigenes Kapitel . Lies es, wenn du so große Zahlen brauchst. +<<<<<<< HEAD ```smart header="Compatability issues" Im Moment wird `BigInt` in Firefox/Chrome/Edge unterstützt, aber nicht in Safari/IE. +======= +```smart header="Compatibility issues" +Right now `BigInt` is supported in Firefox/Chrome/Edge, but not in Safari/IE. +>>>>>>> 58f6599df71b8d50417bb0a52b1ebdc995614017 ``` ## String @@ -127,7 +136,11 @@ In diesem Kapitel werden wir uns eingehender mit Strings befassen . ```smart header="Es gibt keinen *Zeichen*-Typ." In einigen Sprachen gibt es einen speziellen "Zeichen"-Typ für ein einzelnes Zeichen. In der C-Sprache und in Java heißt er beispielsweise "char". +<<<<<<< HEAD In JavaScript gibt es so einen Typ nicht. Es gibt nur einen Typ: `string`. Eine Zeichenfolge kann aus nur einem oder mehreren Zeichen bestehen. +======= +In JavaScript, there is no such type. There's only one type: `string`. A string may consist of zero characters (be empty), one character or many of them. +>>>>>>> 58f6599df71b8d50417bb0a52b1ebdc995614017 ``` ## Boolean (logische Werte) diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md index ca2cd8b6c..b5f71c2d9 100644 --- a/1-js/02-first-steps/06-alert-prompt-confirm/article.md +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -4,7 +4,11 @@ Da wir den Browser als unsere Demo-Umgebung verwenden, wollen wir einige Funktio ## alert +<<<<<<< HEAD Diese haben wir bereits gesehen. Es zeigt eine Meldung an und wartet darauf, dass der Benutzer "OK" drückt. +======= +This one we've seen already. It shows a message and waits for the user to press "OK". +>>>>>>> 58f6599df71b8d50417bb0a52b1ebdc995614017 Zum Beispiel: diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 30287ccba..7327243b1 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -1,4 +1,4 @@ -# Conditional operators: if, '?' +# Conditional branching: if, '?' Sometimes, we need to perform different actions based on different conditions. diff --git a/1-js/02-first-steps/11-logical-operators/article.md b/1-js/02-first-steps/11-logical-operators/article.md index 066c17610..1ce244fa8 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -223,8 +223,13 @@ Die Präzedenz der Operators UND `&&` ist höher als die von ODER `||`. Der Code `a && b || c && d` verhält sich daher i.w. so, als ob die Ausdrücke mit `&&` in Klammern gesetzt würden: `(a && b) || (c && d)`. ```` +<<<<<<< HEAD ````warn header="Ersetze `if` nicht durch || oder &&" Manchmal wird der Operator UND `&&` als "Kürzel zum Schreiben von `if`" verwendet. +======= +````warn header="Don't replace `if` with || or &&" +Sometimes, people use the AND `&&` operator as a "shorter way to write `if`". +>>>>>>> 58f6599df71b8d50417bb0a52b1ebdc995614017 Beispiel: diff --git a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md index f0f6687a3..c72dd91d6 100644 --- a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -68,7 +68,7 @@ Which behavior is better depends on a particular use case. When zero height is a ## Precedence -The precedence of the `??` operator is rather low: `7` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). +The precedence of the `??` operator is rather low: `5` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). So `??` is evaluated after most other operations, but before `=` and `?`. diff --git a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md index 764e36c63..4facc8b29 100644 --- a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md +++ b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md @@ -12,7 +12,7 @@ function pow(x,n) // <- no space between arguments let x=prompt("x?",''), n=prompt("n?",'') // <-- technically possible, // but better make it 2 lines, also there's no spaces and missing ; -if (n<0) // <- no spaces inside (n < 0), and should be extra line above it +if (n<=0) // <- no spaces inside (n <= 0), and should be extra line above it { // <- figure bracket on a separate line // below - long lines can be split into multiple lines for improved readability alert(`Power ${n} is not supported, please enter an integer number greater than zero`); @@ -39,7 +39,7 @@ function pow(x, n) { let x = prompt("x?", ""); let n = prompt("n?", ""); -if (n < 0) { +if (n <= 0) { alert(`Power ${n} is not supported, please enter an integer number greater than zero`); } else { diff --git a/1-js/03-code-quality/04-ninja-code/article.md b/1-js/03-code-quality/04-ninja-code/article.md index 982cc0214..96fdf4143 100644 --- a/1-js/03-code-quality/04-ninja-code/article.md +++ b/1-js/03-code-quality/04-ninja-code/article.md @@ -1,7 +1,7 @@ # Ninja code -```quote author="Confucius" +```quote author="Confucius (Analects)" Learning without thought is labor lost; thought without learning is perilous. ``` @@ -104,8 +104,8 @@ A quick read of such code becomes impossible. And when there's a typo... Ummm... ## Smart synonyms -```quote author="Confucius" -The hardest thing of all is to find a black cat in a dark room, especially if there is no cat. +```quote author="Laozi (Tao Te Ching)" +The Tao that can be told is not the eternal Tao. The name that can be named is not the eternal name. ``` Using *similar* names for *same* things makes life more interesting and shows your creativity to the public. diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md index 672e26d43..e20e5a5d8 100644 --- a/1-js/04-object-basics/03-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -23,7 +23,7 @@ Simply put, "reachable" values are those that are accessible or usable somehow. 2. Any other value is considered reachable if it's reachable from a root by a reference or by a chain of references. - For instance, if there's an object in a local variable, and that object has a property referencing another object, that object is considered reachable. And those that it references are also reachable. Detailed examples to follow. + For instance, if there's an object in a global variable, and that object has a property referencing another object, that object is considered reachable. And those that it references are also reachable. Detailed examples to follow. There's a background process in the JavaScript engine that is called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that have become unreachable. diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md index 974689020..0d832e687 100644 --- a/1-js/04-object-basics/07-optional-chaining/article.md +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -80,7 +80,7 @@ If there's no variable `user` at all, then `user?.anything` triggers an error: // ReferenceError: user is not defined user?.address; ``` -There must be `let/const/var user`. The optional chaining works only for declared variables. +There must be `let/const/var user`. The optional chaining works only for declared variables. ```` ## Short-circuiting diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md index 1ed73ed4b..e469bb0ba 100644 --- a/1-js/04-object-basics/08-symbol/article.md +++ b/1-js/04-object-basics/08-symbol/article.md @@ -121,7 +121,7 @@ user.id = "Their id value" // Boom! overwritten by another script! ``` -### Symbols in a literal +### Symbols in an object literal If we want to use a symbol in an object literal `{...}`, we need square brackets around it. @@ -133,7 +133,7 @@ let id = Symbol("id"); let user = { name: "John", *!* - [id]: 123 // not "id: 123" + [id]: 123 // not "id": 123 */!* }; ``` diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index f6b715ce5..36b6c6460 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -46,7 +46,7 @@ There are three variants of type conversion, so-called "hints", described in the `"default"` : Occurs in rare cases when the operator is "not sure" what type to expect. - For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if the a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. + For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used. diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 33498f40a..43c4c9df6 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -193,7 +193,7 @@ An array is a special kind of object. The square brackets used to access a prope They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object. -Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object. +Remember, there are only eight basic data types in JavaScript (see the [Data types](info:types) chapter for more info). Array is an object and thus behaves like an object. For instance, it is copied by reference: diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js index 45ef1619d..f62452a5f 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js @@ -10,14 +10,14 @@ function Calculator() { let split = str.split(' '), a = +split[0], op = split[1], - b = +split[2] + b = +split[2]; if (!this.methods[op] || isNaN(a) || isNaN(b)) { return NaN; } return this.methods[op](a, b); - } + }; this.addMethod = function(name, func) { this.methods[name] = func; diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 8a38516e1..5e464ac20 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -293,7 +293,7 @@ alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs) 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. + - 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 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`. @@ -304,4 +304,4 @@ Objects that have indexed properties and `length` are called *array-like*. Such If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract. -`Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. +`Array.from(obj[, mapFn, thisArg])` makes a real `Array` from an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md index 9bb1d749c..bed449453 100644 --- a/1-js/05-data-types/11-date/1-new-date/solution.md +++ b/1-js/05-data-types/11-date/1-new-date/solution.md @@ -2,7 +2,17 @@ The `new Date` constructor uses the local time zone. So the only important thing So February has number 1. +Here's an example with numbers as date components: + +```js run +//new Date(year, month, date, hour, minute, second, millisecond) +let d1 = new Date(2012, 1, 20, 3, 12); +alert( d1 ); +``` +We could also create a date from a string, like this: + ```js run -let d = new Date(2012, 1, 20, 3, 12); -alert( d ); +//new Date(datastring) +let d2 = new Date("February 20, 2012 03:12:00"); +alert( d2 ); ``` diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md index 404bae80b..346e4060a 100644 --- a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md @@ -15,7 +15,7 @@ function func() { func(); ``` -In this example we can observe the peculiar difference between a "non-existing" and "unitialized" variable. +In this example we can observe the peculiar difference between a "non-existing" and "uninitialized" variable. As you may have read in the article [](info:closure), a variable starts in the "uninitialized" state from the moment when the execution enters a code block (or a function). And it stays uninitalized until the corresponding `let` statement. diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 90d1d735e..4d06c9db7 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -1,11 +1,15 @@ -# Variable scope +# Variable scope, closure -JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, passed as an argument to another function and called from a totally different place of code later. +JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created at any moment, passed as an argument to another function, and then called from a totally different place of code later. -We already know that a function can access variables outside of it. +We already know that a function can access variables outside of it ("outer" variables). -Now let's expand our knowledge to include more complex scenarios. +But what happens if outer variables change since a function is created? Will the function get newer values or the old ones? + +And what if a function is passed along as a parameter and called from another place of code, will it get access to outer variables at the new place? + +Let's expand our knowledge to understand these scenarios and more complex ones. ```smart header="We'll talk about `let/const` variables here" In JavaScript, there are 3 ways to declare a variable: `let`, `const` (the modern ones), and `var` (the remnant of the past). diff --git a/1-js/06-advanced-functions/05-global-object/article.md b/1-js/06-advanced-functions/05-global-object/article.md index 3d195a978..679db05c1 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -5,7 +5,7 @@ The global object provides variables and functions that are available anywhere. In a browser it is named `window`, for Node.js it is `global`, for other environments it may have another name. -Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. In some browsers, namely non-Chromium Edge, `globalThis` is not yet supported, but can be easily polyfilled. +Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers. We'll use `window` here, assuming that our environment is a browser. If your script may run in other environments, it's better to use `globalThis` instead. @@ -81,7 +81,7 @@ if (!window.Promise) { That includes JavaScript built-ins, such as `Array` and environment-specific values, such as `window.innerHeight` -- the window height in the browser. - The global object has a universal name `globalThis`. - ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). As `globalThis` is a recent proposal, it's not supported in non-Chromium Edge (but can be polyfilled). + ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). - We should store values in the global object only if they're truly global for our project. And keep their number at minimum. - In-browser, unless we're using [modules](info:modules), global functions and variables declared with `var` become a property of the global object. - To make our code future-proof and easier to understand, we should access properties of the global object directly, as `window.x`. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 550bf52da..347a5e64f 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -6,6 +6,8 @@ importance: 5 The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments. +In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`). + For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`. Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call. @@ -14,7 +16,7 @@ Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there a ...And it will get the arguments of the very last call, other calls are ignored. -Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce): +Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)): ```js let f = _.debounce(alert, 1000); @@ -25,7 +27,6 @@ setTimeout( () => f("c"), 500); // debounced function waits 1000ms after the last call and then runs: alert("c") ``` - Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished. There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result. @@ -43,9 +44,8 @@ See? The second input calls the debounced function, so its content is processed So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else. - It waits the given time after the last call, and then runs its function, that can process the result. The task is to implement `debounce` decorator. -Hint: that's just a few lines if you think about it :) \ No newline at end of file +Hint: that's just a few lines if you think about it :) diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index 6cb664fdb..6df7af132 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -6,12 +6,14 @@ importance: 5 Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper. -When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. +When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. The difference with debounce is that it's completely different decorator: - `debounce` runs the function once after the "cooldown" period. Good for processing the final result. - `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often. +In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds. + Let's check the real-life application to better understand that requirement and to see where it comes from. **For instance, we want to track mouse movements.** diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 787c7d68e..8de8e6fd1 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -167,7 +167,7 @@ sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John! // even if the value of user changes within 1 second -// sayHi uses the pre-bound value +// sayHi uses the pre-bound value which is reference to the old user object user = { sayHi() { alert("Another user in setTimeout!"); } }; @@ -202,7 +202,7 @@ for (let key in user) { } ``` -JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash. +JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll) in lodash. ```` ## Partial functions diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 710390f15..e57808051 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -197,6 +197,9 @@ alert(admin.fullName); // John Smith (*) // setter triggers! admin.fullName = "Alice Cooper"; // (**) + +alert(admin.fullName); // Alice Cooper , state of admin modified +alert(user.fullName); // John Smith , state of user protected ``` Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called. diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index 378936c9a..6cf7aebb4 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -33,7 +33,9 @@ We can check it like this: let obj = {}; alert(obj.__proto__ === Object.prototype); // true -// obj.toString === obj.__proto__.toString == Object.prototype.toString + +alert(obj.toString === obj.__proto__.toString); //true +alert(obj.toString === Object.prototype.toString); //true ``` Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`: diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index d517e1564..e460ef016 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -7,7 +7,7 @@ The `__proto__` is considered outdated and somewhat deprecated (in browser-only The modern methods are: -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. +- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`. - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`. @@ -175,7 +175,7 @@ alert(Object.keys(chineseDictionary)); // hello,bye Modern methods to set up and directly access the prototype are: -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with a given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors. +- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with a given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors. - [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter). - [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter). diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 3ce6d2995..69ca4eab8 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -364,7 +364,7 @@ Why is there the difference? Well, the reason is in the field initialization order. The class field is initialized: - Before constructor for the base class (that doesn't extend anything), -- Imediately after `super()` for the derived class. +- Immediately after `super()` for the derived class. In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`. @@ -545,7 +545,7 @@ Here's the demo of a wrong `super` result after copying: ```js run let animal = { sayHi() { - console.log(`I'm an animal`); + alert(`I'm an animal`); } }; @@ -559,7 +559,7 @@ let rabbit = { let plant = { sayHi() { - console.log("I'm a plant"); + alert("I'm a plant"); } }; diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index aa973da06..dd3d61ca6 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -190,7 +190,7 @@ For most environment-specific objects, there is such a property. Here are some b ```js run // toStringTag for the environment-specific object and class: -alert( window[Symbol.toStringTag]); // window +alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 60f6f7c4a..d43b96c96 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -154,7 +154,7 @@ let eventMixin = { * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { - if (!this._eventHandlers || !this._eventHandlers[eventName]) { + if (!this._eventHandlers?.[eventName]) { return; // no handlers for that event name } diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index 7d41b71b1..3a2dc4ed4 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -363,7 +363,7 @@ The "rethrowing" technique can be explained in more detail as: 1. Catch gets all errors. 2. In the `catch(err) {...}` block we analyze the error object `err`. -2. If we don't know how to handle it, we do `throw err`. +3. If we don't know how to handle it, we do `throw err`. Usually, we can check the error type using the `instanceof` operator: diff --git a/1-js/11-async/04-promise-error-handling/article.md b/1-js/11-async/04-promise-error-handling/article.md index 7c6af6a33..9f7159af9 100644 --- a/1-js/11-async/04-promise-error-handling/article.md +++ b/1-js/11-async/04-promise-error-handling/article.md @@ -122,7 +122,7 @@ Here the `.catch` block finishes normally. So the next successful `.then` handle In the example below we see the other situation with `.catch`. The handler `(*)` catches the error and just can't handle it (e.g. it only knows how to handle `URIError`), so it throws it again: ```js run -// the execution: catch -> catch -> then +// the execution: catch -> catch new Promise((resolve, reject) => { throw new Error("Whoops!"); diff --git a/1-js/11-async/08-async-await/article.md b/1-js/11-async/08-async-await/article.md index dfb8a9d59..29bfcaf51 100644 --- a/1-js/11-async/08-async-await/article.md +++ b/1-js/11-async/08-async-await/article.md @@ -67,7 +67,7 @@ f(); The function execution "pauses" at the line `(*)` and resumes when the promise settles, with `result` becoming its result. So the code above shows "done!" in one second. -Let's emphasize: `await` literally makes JavaScript wait until the promise settles, and then go on with the result. That doesn't cost any CPU resources, because the engine can do other jobs in the meantime: execute other scripts, handle events, etc. +Let's emphasize: `await` literally suspends the function execution until the promise settles, and then resumes it with the promise result. That doesn't cost any CPU resources, because the JavaScript engine can do other jobs in the meantime: execute other scripts, handle events, etc. It's just a more elegant syntax of getting the promise result than `promise.then`, easier to read and write. @@ -83,7 +83,7 @@ function f() { } ``` -We will get this error if we do not put `async` before a function. As said, `await` only works inside an `async function`. +We may get this error if we forget to put `async` before a function. As said, `await` only works inside an `async` function. ```` Let's take the `showAvatar()` example from the chapter and rewrite it using `async/await`: @@ -139,9 +139,8 @@ But we can wrap it into an anonymous async function, like this: ... })(); ``` - - ```` + ````smart header="`await` accepts \"thenables\"" 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`. diff --git a/1-js/12-generators-iterators/2-async-iterators-generators/article.md b/1-js/12-generators-iterators/2-async-iterators-generators/article.md index 97c8e12a5..704ba0672 100644 --- a/1-js/12-generators-iterators/2-async-iterators-generators/article.md +++ b/1-js/12-generators-iterators/2-async-iterators-generators/article.md @@ -1,36 +1,48 @@ -# Async iterators and generators +# Async iteration and generators -Asynchronous iterators allow us to iterate over data that comes asynchronously, on-demand. Like, for instance, when we download something chunk-by-chunk over a network. And asynchronous generators make it even more convenient. +Asynchronous iteration allow us to iterate over data that comes asynchronously, on-demand. Like, for instance, when we download something chunk-by-chunk over a network. And asynchronous generators make it even more convenient. Let's see a simple example first, to grasp the syntax, and then review a real-life use case. -## Async iterators +## Recall iterables -Asynchronous iterators are similar to regular iterators, with a few syntactic differences. +Let's recall the topic about iterables. -A "regular" iterable object, as described in the chapter , looks like this: +The idea is that we have an object, such as `range` here: +```js +let range = { + from: 1, + to: 5 +}; +``` + +...And we'd like to use `for..of` loop on it, such as `for(value of range)`, to get values from `1` to `5`. + +In other words, we want to add an *iteration ability* to the object. + +That can be implemented using a special method with the name `Symbol.iterator`: + +- This method is called in by the `for..of` construct when the loop is started, and it should return an object with the `next` method. +- For each iteration, the `next()` method is invoked for the next value. +- The `next()` should return a value in the form `{done: true/false, value:}`, where `done:true` means the end of the loop. + +Here's an implementation for the iterable `range`: ```js run let range = { from: 1, to: 5, - // for..of calls this method once in the very beginning *!* - [Symbol.iterator]() { + [Symbol.iterator]() { // called once, in the beginning of for..of */!* - // ...it returns the iterator object: - // onward, for..of works only with that object, - // asking it for next values using next() return { current: this.from, last: this.to, - // next() is called on each iteration by the for..of loop *!* - next() { // (2) - // it should return the value as an object {done:.., value :...} + next() { // called every iteration, to get the next value */!* if (this.current <= this.last) { return { done: false, value: this.current++ }; @@ -47,40 +59,44 @@ for(let value of range) { } ``` -If necessary, please refer to the [chapter about iterables](info:iterable) for details about regular iterators. +If anything is unclear, please visit the chapter [](info:iterable), it gives all the details about regular iterables. + +## Async iterables + +Asynchronous iteration is needed when values come asynchronously: after `setTimeout` or another kind of delay. + +The most common case is that the object needs to make a network request to deliver the next value, we'll see a real-life example of it a bit later. -To make the object iterable asynchronously: -1. We need to use `Symbol.asyncIterator` instead of `Symbol.iterator`. -2. `next()` should return a promise. +To make an object iterable asynchronously: + +1. Use `Symbol.asyncIterator` instead of `Symbol.iterator`. +2. The `next()` method should return a promise (to be fulfilled with the next value). + - The `async` keyword handles it, we can simply make `async next()`. 3. To iterate over such an object, we should use a `for await (let item of iterable)` loop. + - Note the `await` word. + +As a starting example, let's make an iterable `range` object, similar like the one before, but now it will return values asynchronously, one per second. -Let's make an iterable `range` object, like the one before, but now it will return values asynchronously, one per second: +All we need to do is to perform a few replacements in the code above: ```js run let range = { from: 1, to: 5, - // for await..of calls this method once in the very beginning *!* [Symbol.asyncIterator]() { // (1) */!* - // ...it returns the iterator object: - // onward, for await..of works only with that object, - // asking it for next values using next() return { current: this.from, last: this.to, - // next() is called on each iteration by the for await..of loop *!* async next() { // (2) - // it should return the value as an object {done:.., value :...} - // (automatically wrapped into a promise by async) */!* *!* - // can use await inside, do async stuff: + // note: we can use "await" inside the async next: await new Promise(resolve => setTimeout(resolve, 1000)); // (3) */!* @@ -112,7 +128,7 @@ As we can see, the structure is similar to regular iterators: 3. The `next()` method doesn't have to be `async`, it may be a regular method returning a promise, but `async` allows us to use `await`, so that's convenient. Here we just delay for a second `(3)`. 4. To iterate, we use `for await(let value of range)` `(4)`, namely add "await" after "for". It calls `range[Symbol.asyncIterator]()` once, and then its `next()` for values. -Here's a small cheatsheet: +Here's a small table with the differences: | | Iterators | Async iterators | |-------|-----------|-----------------| @@ -128,14 +144,20 @@ For instance, a spread syntax won't work: alert( [...range] ); // Error, no Symbol.iterator ``` -That's natural, as it expects to find `Symbol.iterator`, same as `for..of` without `await`. Not `Symbol.asyncIterator`. +That's natural, as it expects to find `Symbol.iterator`, not `Symbol.asyncIterator`. + +It's also the case for `for..of`: the syntax without `await` needs `Symbol.iterator`. ```` -## Async generators +## Recall generators + +Now let's recall generators, as they allow to make iteration code much shorter. Most of the time, when we'd like to make an iterable, we'll use generators. + +For sheer simplicity, omitting some important stuff, they are "functions that generate (yield) values". They are explained in detail in the chapter [](info:generators). -As we already know, JavaScript also supports generators, and they are iterable. +Generators are labelled with `function*` (note the start) and use `yield` to generate a value, then we can use `for..of` to loop over them. -Let's recall a sequence generator from the chapter [](info:generators). It generates a sequence of values from `start` to `end`: +This example generates a sequence of values from `start` to `end`: ```js run function* generateSequence(start, end) { @@ -149,11 +171,54 @@ for(let value of generateSequence(1, 5)) { } ``` -In regular generators we can't use `await`. All values must come synchronously: there's no place for delay in `for..of`, it's a synchronous construct. +As we already know, to make an object iterable, we should add `Symbol.iterator` to it. -But what if we need to use `await` in the generator body? To perform network requests, for instance. +```js +let range = { + from: 1, + to: 5, +*!* + [Symbol.iterator]() { + return + } +*/!* +} +``` -No problem, just prepend it with `async`, like this: +A common practice for `Symbol.iterator` is to return a generator, it makes the code shorter, as you can see: + +```js run +let range = { + from: 1, + to: 5, + + *[Symbol.iterator]() { // a shorthand for [Symbol.iterator]: function*() + for(let value = this.from; value <= this.to; value++) { + yield value; + } + } +}; + +for(let value of range) { + alert(value); // 1, then 2, then 3, then 4, then 5 +} +``` + +Please see the chapter [](info:generators) if you'd like more details. + +In regular generators we can't use `await`. All values must come synchronously, as required by the `for..of` construct. + +What if we'd like to generate values asynchronously? From network requests, for instance. + +Let's switch to asynchronous generators to make it possible. + +## Async generators (finally) + +For most practical applications, when we'd like to make an object that asynchronously generates a sequence of values, we can use an asynchronous generator. + +The syntax is simple: prepend `function*` with `async`. That makes the generator asynchronous. + +And then use `for await (...)` to iterate over it, like this: ```js run *!*async*/!* function* generateSequence(start, end) { @@ -161,7 +226,7 @@ No problem, just prepend it with `async`, like this: for (let i = start; i <= end; i++) { *!* - // yay, can use await! + // Wow, can use await! await new Promise(resolve => setTimeout(resolve, 1000)); */!* @@ -174,72 +239,43 @@ No problem, just prepend it with `async`, like this: let generator = generateSequence(1, 5); for *!*await*/!* (let value of generator) { - alert(value); // 1, then 2, then 3, then 4, then 5 + alert(value); // 1, then 2, then 3, then 4, then 5 (with delay between) } })(); ``` -Now we have the async generator, iterable with `for await...of`. +As the generator is asynchronous, we can use `await` inside it, rely on promises, perform network requests and so on. -It's indeed very simple. We add the `async` keyword, and the generator now can use `await` inside of it, rely on promises and other async functions. +````smart header="Under-the-hood difference" +Technically, if you're an advanced reader who remembers the details about generators, there's an internal difference. -Technically, another difference of an async generator is that its `generator.next()` method is now asynchronous also, it returns promises. +For async generators, the `generator.next()` method is asynchronous, it returns promises. In a regular generator we'd use `result = generator.next()` to get values. In an async generator, we should add `await`, like this: ```js result = await generator.next(); // result = {value: ..., done: true/false} ``` +That's why async generators work with `for await...of`. +```` -## Async iterables - -As we already know, to make an object iterable, we should add `Symbol.iterator` to it. - -```js -let range = { - from: 1, - to: 5, -*!* - [Symbol.iterator]() { - return - } -*/!* -} -``` - -A common practice for `Symbol.iterator` is to return a generator, rather than a plain object with `next` as in the example before. - -Let's recall an example from the chapter [](info:generators): - -```js run -let range = { - from: 1, - to: 5, - - *[Symbol.iterator]() { // a shorthand for [Symbol.iterator]: function*() - for(let value = this.from; value <= this.to; value++) { - yield value; - } - } -}; +### Async iterable range -for(let value of range) { - alert(value); // 1, then 2, then 3, then 4, then 5 -} -``` +Regular generators can be used as `Symbol.iterator` to make the iteration code shorter. -Here a custom object `range` is iterable, and the generator `*[Symbol.iterator]` implements the logic for listing values. +Similar to that, async generators can be used as `Symbol.asyncIterator` to implement the asynchronous iteration. -If we'd like to add async actions into the generator, then we should replace `Symbol.iterator` with async `Symbol.asyncIterator`: +For instance, we can make the `range` object generate values asynchronously, once per second, by replacing synchronous `Symbol.iterator` with asynchronous `Symbol.asyncIterator`: ```js run let range = { from: 1, to: 5, + // this line is same as [Symbol.asyncIterator]: async function*() { *!* - async *[Symbol.asyncIterator]() { // same as [Symbol.asyncIterator]: async function*() + async *[Symbol.asyncIterator]() { */!* for(let value = this.from; value <= this.to; value++) { @@ -262,31 +298,39 @@ let range = { Now values come with a delay of 1 second between them. -## Real-life example +```smart +Technically, we can add both `Symbol.iterator` and `Symbol.asyncIterator` to the object, so it's both synchronously (`for..of`) and asynchronously (`for await..of`) iterable. + +In practice though, that would be an weird thing to do. +``` + +## Real-life example: paginated data -So far we've seen simple examples, to gain basic understanding. Now let's review a real-life use case. +So far we've seen basic examples, to gain understanding. Now let's review a real-life use case. There are many online services that deliver paginated data. For instance, when we need a list of users, a request returns a pre-defined count (e.g. 100 users) - "one page", and provides a URL to the next page. -This pattern is very common. It's not about users, but just about anything. For instance, GitHub allows us to retrieve commits in the same, paginated fashion: +This pattern is very common. It's not about users, but just about anything. + +For instance, GitHub allows us to retrieve commits in the same, paginated fashion: -- We should make a request to URL in the form `https://api.github.com/repos//commits`. +- We should make a request to `fetch` in the form `https://api.github.com/repos//commits`. - It responds with a JSON of 30 commits, and also provides a link to the next page in the `Link` header. - Then we can use that link for the next request, to get more commits, and so on. -But we'd like to have a simpler API: an iterable object with commits, so that we could go over them like this: +For our code, we'd like to have a simpler way to get commits. -```js -let repo = 'javascript-tutorial/en.javascript.info'; // GitHub repository to get commits from +Let's make a function `fetchCommits(repo)` that gets commits for us, making requests whenever needed. And let it care about all pagination stuff. For us it'll be a simple async iteration `for await..of`. -for await (let commit of fetchCommits(repo)) { +So the usage will be like this: + +```js +for await (let commit of fetchCommits("username/repository")) { // process commit } ``` -We'd like to make a function `fetchCommits(repo)` that gets commits for us, making requests whenever needed. And let it care about all pagination stuff. For us it'll be a simple `for await..of`. - -With async generators that's pretty easy to implement: +Here's such function, implemented as async generator: ```js async function* fetchCommits(repo) { @@ -294,7 +338,7 @@ async function* fetchCommits(repo) { while (url) { const response = await fetch(url, { // (1) - headers: {'User-Agent': 'Our script'}, // github requires user-agent header + headers: {'User-Agent': 'Our script'}, // github needs any user-agent header }); const body = await response.json(); // (2) response is JSON (array of commits) @@ -312,10 +356,16 @@ async function* fetchCommits(repo) { } ``` -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. +More explanations about how it works: + +1. We use the browser [fetch](info:fetch) method to download the commits. + + - The initial URL is `https://api.github.com/repos//commits`, and the next page will be in the `Link` header of the response. + - The `fetch` method allows us to supply authorization and other headers if needed -- here GitHub requires `User-Agent`. +2. The commits are returned in JSON format. +3. We should get the next page URL from the `Link` header of the response. It has a special format, so we use a regular expression 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 the received commits one by one, and when they finish, the next `while(url)` iteration will trigger, making one more request. An example of use (shows commit authors in console): @@ -336,7 +386,9 @@ An example of use (shows commit authors in console): })(); ``` -That's just what we wanted. The internal mechanics of paginated requests is invisible from the outside. For us it's just an async generator that returns commits. +That's just what we wanted. + +The internal mechanics of paginated requests is invisible from the outside. For us it's just an async generator that returns commits. ## Summary 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 827cf35e3..d7093c0c3 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,8 +1,8 @@ -# Error on reading non-existant property +# Error on reading non-existent property -Usually, an attempt to read a non-existant property returns `undefined`. +Usually, an attempt to read a non-existent property returns `undefined`. -Create a proxy that throws an error for an attempt to read of a non-existant property instead. +Create a proxy that throws an error for an attempt to read of a non-existent property instead. That can help to detect programming mistakes early. diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index a4aec1fe5..3f7ef63b8 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -840,7 +840,7 @@ So there's no such problem when proxying an array. ### Private fields -The similar thing happens with private class fields. +A similar thing happens with private class fields. For example, `getName()` method accesses the private `#name` property and breaks after proxying: @@ -963,7 +963,7 @@ revoke(); 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. +A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer 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 a proxy object: diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md index ef843ab79..c680c17f9 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -106,9 +106,3 @@ That's for the subsequent method call `()` to get the object and set `this` to i For all other operations, the reference type automatically becomes the property value (a function in our case). The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression. - - - - - - result of dot `.` isn't actually a method, but a value of `` needs a way to pass the information about `obj` diff --git a/1-js/99-js-misc/05-bigint/article.md b/1-js/99-js-misc/05-bigint/article.md index 72f06d0c7..062dd6017 100644 --- a/1-js/99-js-misc/05-bigint/article.md +++ b/1-js/99-js-misc/05-bigint/article.md @@ -50,7 +50,7 @@ The conversion operations are always silent, never give errors, but if the bigin ````smart header="The unary plus is not supported on bigints" The unary plus operator `+value` is a well-known way to convert `value` to a number. -On bigints it's not supported, to avoid confusion: +In order to avoid confusion, it's not supported on bigints: ```js run let bigint = 1n; diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index 9a2677df4..75ce1fbb0 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -61,7 +61,7 @@ let div = document.createElement('div'); // 2. Set its class to "alert" div.className = "alert"; -// Fill it with the content +// 3. Fill it with the content div.innerHTML = "Hi there! You've read an important message."; ``` diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index fb55edf57..4775ff0eb 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -88,8 +88,8 @@ As you can see, `left/top` do not equal `x/y` in such case. In practice though, `elem.getBoundingClientRect()` always returns positive width/height, here we mention negative `width/height` only for you to understand why these seemingly duplicate properties are not actually duplicates. ``` -```warn header="Internet Explorer and Edge: no support for `x/y`" -Internet Explorer and Edge don't support `x/y` properties for historical reasons. +```warn header="Internet Explorer: no support for `x/y`" +Internet Explorer doesn't support `x/y` properties for historical reasons. 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()`. ``` diff --git a/2-ui/2-events/01-introduction-browser-events/article.md b/2-ui/2-events/01-introduction-browser-events/article.md index cf16c9836..19394e49e 100644 --- a/2-ui/2-events/01-introduction-browser-events/article.md +++ b/2-ui/2-events/01-introduction-browser-events/article.md @@ -236,7 +236,7 @@ element.removeEventListener(event, handler, [options]); ````warn header="Removal requires the same function" To remove a handler we should pass exactly the same function as was assigned. -That doesn't work: +This doesn't work: ```js no-beautify elem.addEventListener( "click" , () => alert('Thanks!')); diff --git a/2-ui/2-events/02-bubbling-and-capturing/article.md b/2-ui/2-events/02-bubbling-and-capturing/article.md index decc25154..e203a4eb4 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -206,7 +206,7 @@ When an event happens -- the most nested element where it happens gets labeled a - Then the event moves down from the document root to `event.target`, calling handlers assigned with `addEventListener(..., true)` on the way (`true` is a shorthand for `{capture: true}`). - Then handlers are called on the target element itself. -- Then the event bubbles up from `event.target` up to the root, calling handlers assigned using `on` and `addEventListener` without the 3rd argument or with the 3rd argument `false/{capture:false}`. +- Then the event bubbles up from `event.target` to the root, calling handlers assigned using `on` and `addEventListener` without the 3rd argument or with the 3rd argument `false/{capture:false}`. Each handler can access `event` object properties: @@ -220,6 +220,6 @@ The capturing phase is used very rarely, usually we handle events on bubbling. A In real world, when an accident happens, local authorities react first. They know best the area where it happened. Then higher-level authorities if needed. -The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular `` may be suited for that exactly ``, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last. +The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular `` may be suited for that exactly ``, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last one. Bubbling and capturing lay the foundation for "event delegation" -- an extremely powerful event handling pattern that we study in the next chapter. diff --git a/2-ui/2-events/05-dispatch-events/article.md b/2-ui/2-events/05-dispatch-events/article.md index 1a8e92ef1..fa6a0308b 100644 --- a/2-ui/2-events/05-dispatch-events/article.md +++ b/2-ui/2-events/05-dispatch-events/article.md @@ -162,7 +162,7 @@ Besides, the event class describes "what kind of event" it is, and if the event ## event.preventDefault() -Many browser events have a "default action", such as nagivating to a link, starting a selection, and so on. +Many browser events have a "default action", such as navigating to a link, starting a selection, and so on. For new, custom events, there are definitely no default browser actions, but a code that dispatches such event may have its own plans what to do after triggering the event. @@ -187,7 +187,6 @@ Any handler can listen for that event with `rabbit.addEventListener('hide',...)` @@ -68,40 +70,44 @@ The following example demonstrates that: ``` 1. The page content shows up immediately. -2. `DOMContentLoaded` waits for the deferred script. It only triggers when the script `(2)` is downloaded and executed. +2. `DOMContentLoaded` event handler waits for the deferred script. It only triggers when the script is downloaded and executed. -Deferred scripts keep their relative order, just like regular scripts. +**Deferred scripts keep their relative order, just like regular scripts.** -So, if we have a long script first, and then a smaller one, then the latter one waits. +Let's say, we have two deferred scripts: the `long.js` and then `small.js`: ```html ``` -```smart header="The small script downloads first, runs second" -Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The `small.js` probably makes it first. +Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The `small.js` probably finishes first. -But the specification requires scripts to execute in the document order, so it waits for `long.js` to execute. -``` +...But the `defer` atribute, besides telling the browser "not to block", ensures that the relative order is kept. So even though `small.js` loads first, it still waits and runs after `long.js` executes. + +That may be important for cases when we need to load a JavaScript library and then a script that depends on it. ```smart header="The `defer` attribute is only for external scripts" The `defer` attribute is ignored if the ` ``` - ## Dynamic scripts + +There's one more important way of adding a script to the page. -We can also add a script dynamically using JavaScript: +We can create a script and append it to the document dynamically using JavaScript: ```js run let script = document.createElement('script'); @@ -146,20 +153,11 @@ That is: - They don't wait for anything, nothing waits for them. - The script that loads first -- runs first ("load-first" order). +This can be changed if we explicitly set `script.async=true`. Then scripts will be executed in the document order, just like `defer`. -```js run -let script = document.createElement('script'); -script.src = "/article/script-async-defer/long.js"; - -*!* -script.async = false; -*/!* - -document.body.append(script); -``` - -For example, here we add two scripts. Without `script.async=false` they would execute in load-first order (the `small.js` probably first). But with that flag the order is "as in the document": +In this example, `loadScript(src)` function adds a script and also sets `async` to `false`. +So `long.js` always runs first (as it's added first): ```js run function loadScript(src) { @@ -174,6 +172,10 @@ loadScript("/article/script-async-defer/long.js"); loadScript("/article/script-async-defer/small.js"); ``` +Without `script.async=false`, scripts would execute in default, load-first order (the `small.js` probably first). + +Again, as with the `defer`, the order matters if we'd like to load a library and then another script that depends on it. + ## Summary @@ -186,12 +188,14 @@ But there are also essential differences between them: | `async` | *Load-first order*. Their document order doesn't matter -- which loads first | Irrelevant. May load and execute while the document has not yet been fully downloaded. That happens if scripts are small or cached, and the document is long enough. | | `defer` | *Document order* (as they go in the document). | Execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. | +In practice, `defer` is used for scripts that need the whole DOM and/or their relative execution order is important. + +And `async` is used for independent scripts, like counters or ads. And their relative execution order does not matter. + ```warn header="Page without scripts should be usable" -Please note that if you're using `defer`, then the page is visible *before* the script loads. +Please note: if you're using `defer` or `async`, then user will see the the page *before* the script loads. -So the user may read the page, but some graphical components are probably not ready yet. +In such case, some graphical components are probably not initialized yet. -There should be "loading" indications in the proper places, and disabled buttons should show as such, so the user can clearly see what's ready and what's not. +Don't forget to put "loading" indication and disable buttons that aren't functional yet. Let the user clearly see what he can do on the page, and what's still getting ready. ``` - -In practice, `defer` is used for scripts that need the whole DOM and/or their relative execution order is important. And `async` is used for independent scripts, like counters or ads. And their relative execution order does not matter. diff --git a/2-ui/99-ui-misc/01-mutation-observer/article.md b/2-ui/99-ui-misc/01-mutation-observer/article.md index 6a458fa02..3345a1bcc 100644 --- a/2-ui/99-ui-misc/01-mutation-observer/article.md +++ b/2-ui/99-ui-misc/01-mutation-observer/article.md @@ -1,7 +1,7 @@ # Mutation observer -`MutationObserver` is a built-in object that observes a DOM element and fires a callback in case of changes. +`MutationObserver` is a built-in object that observes a DOM element and fires a callback when it detects a change. We'll first take a look at the syntax, and then explore a real-world use case, to see where such thing may be useful. @@ -128,16 +128,16 @@ Such snippet in an HTML markup looks like this: ... ``` -Also we'll use a JavaScript highlighting library on our site, e.g. [Prism.js](https://prismjs.com/). A call to `Prism.highlightElem(pre)` examines the contents of such `pre` elements and adds into them special tags and styles for colored syntax highlighting, similar to what you see in examples here, at this page. +For better readability and at the same time, to beautify it, we'll be using a JavaScript syntax highlighting library on our site, like [Prism.js](https://prismjs.com/). To get syntax highlighting for above snippet in Prism, `Prism.highlightElem(pre)` is called, which examines the contents of such `pre` elements and adds special tags and styles for colored syntax highlighting into those elements, similar to what you see in examples here, on this page. -When exactly to run that highlighting method? We can do it on `DOMContentLoaded` event, or at the bottom of the page. At that moment we have our DOM ready, can search for elements `pre[class*="language"]` and call `Prism.highlightElem` on them: +When exactly should we run that highlighting method? Well, we can do it on `DOMContentLoaded` event, or put the script at the bottom of the page. The moment our DOM is ready, we can search for elements `pre[class*="language"]` and call `Prism.highlightElem` on them: ```js // highlight all code snippets on the page document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem); ``` -Everything's simple so far, right? There are `
` code snippets in HTML, we highlight them.
+Everything's simple so far, right? We find code snippets in HTML and highlight them.
 
 Now let's go on. Let's say we're going to dynamically fetch materials from a server. We'll study methods for that [later in the tutorial](info:fetch). For now it only matters that we fetch an HTML article from a webserver and display it on demand:
 
@@ -162,13 +162,13 @@ snippets.forEach(Prism.highlightElem);
 */!*
 ```
 
-...But imagine, we have many places in the code where we load contents: articles, quizzes, forum posts. Do we need to put the highlighting call everywhere? That's not very convenient, and also easy to forget.
+...But, imagine if we have many places in the code where we load our content - articles, quizzes, forum posts, etc. Do we need to put the highlighting call everywhere, to highlight the code in content after loading? That's not very convenient.
 
-And what if the content is loaded by a third-party module? E.g. we have a forum written by someone else, that loads contents dynamically, and we'd like to add syntax highlighting to it. No one likes to patch third-party scripts.
+And what if the content is loaded by a third-party module? For example, we have a forum written by someone else, that loads content dynamically, and we'd like to add syntax highlighting to it. No one likes patching third-party scripts.
 
 Luckily, there's another option.
 
-We can use `MutationObserver` to automatically detect when code snippets are inserted in the page and highlight them.
+We can use `MutationObserver` to automatically detect when code snippets are inserted into the page and highlight them.
 
 So we'll handle the highlighting functionality in one place, relieving us from the need to integrate it.
 
@@ -236,9 +236,9 @@ There's a method to stop observing the node:
 
 - `observer.disconnect()` -- stops the observation.
 
-When we stop the observing, it might be possible that some changes were not processed by the observer yet.
+When we stop the observing, it might be possible that some changes were not yet processed by the observer. In such cases, we use
 
-- `observer.takeRecords()` -- gets a list of unprocessed mutation records, those that happened, but the callback did not handle them.
+- `observer.takeRecords()` -- gets a list of unprocessed mutation records - those that happened, but the callback has not handled them.
 
 These methods can be used together, like this:
 
@@ -252,14 +252,14 @@ let mutationRecords = observer.takeRecords();
 ```
 
 ```smart header="Garbage collection interaction"
-Observers use weak references to nodes internally. That is: if a node is removed from DOM, and becomes unreachable, then it becomes garbage collected.
+Observers use weak references to nodes internally. That is, if a node is removed from the DOM, and becomes unreachable, then it can be garbage collected.
 
 The mere fact that a DOM node is observed doesn't prevent the garbage collection.
 ```
 
 ## Summary  
 
-`MutationObserver` can react on changes in DOM: attributes, added/removed elements, text content.
+`MutationObserver` can react to changes in DOM - attributes, text content and adding/removing elements.
 
 We can use it to track changes introduced by other parts of our code, as well as to integrate with third-party scripts.
 
diff --git a/2-ui/99-ui-misc/02-selection-range/article.md b/2-ui/99-ui-misc/02-selection-range/article.md
index 6f4d6814b..b908eb6b4 100644
--- a/2-ui/99-ui-misc/02-selection-range/article.md
+++ b/2-ui/99-ui-misc/02-selection-range/article.md
@@ -620,7 +620,7 @@ The second API is very simple, as it works with text.
 The most used recipes are probably:
 
 1. Getting the selection:
-    ```js run
+    ```js
     let selection = document.getSelection();
 
     let cloned = /* element to clone the selected nodes to */;
@@ -632,7 +632,7 @@ The most used recipes are probably:
     }
     ```
 2. Setting the selection:
-    ```js run
+    ```js
     let selection = document.getSelection();
 
     // directly:
diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md
index 4a1625288..3ea0c2c57 100644
--- a/2-ui/99-ui-misc/03-event-loop/article.md
+++ b/2-ui/99-ui-misc/03-event-loop/article.md
@@ -9,7 +9,7 @@ In this chapter we first cover theoretical details about how things work, and th
 
 ## Event Loop
 
-The concept of *event loop* is very simple. There's an endless loop, when JavaScript engine waits for tasks, executes them and then sleeps waiting for more tasks.
+The *event loop* concept is very simple. There's an endless loop, where the JavaScript engine waits for tasks, executes them and then sleeps, waiting for more tasks.
 
 The general algorithm of the engine:
 
@@ -17,7 +17,7 @@ The general algorithm of the engine:
     - execute them, starting with the oldest task.
 2. Sleep until a task appears, then go to 1.
 
-That's a formalization for what we see when browsing a page. JavaScript engine does nothing most of the time, only runs if a script/handler/event activates.
+That's a formalization for what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates.
 
 Examples of tasks:
 
@@ -41,10 +41,10 @@ Tasks from the queue are processed on "first come – first served" basis. When
 So far, quite simple, right?
 
 Two more details:
-1. Rendering never happens while the engine executes a task. Doesn't matter if the task takes a long time. Changes to DOM are painted only after the task is complete.
-2. If a task takes too long, the browser can't do other tasks, process user events, so after a time it raises an alert like "Page Unresponsive" suggesting to kill the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to infinite loop.
+1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete.
+2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after a time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop.
 
-That was a theory. Now let's see how we can apply that knowledge.
+That was the theory. Now let's see how we can apply that knowledge.
 
 ## Use-case 1: splitting CPU-hungry tasks
 
@@ -160,7 +160,7 @@ Finally, we've split a CPU-hungry task into parts - now it doesn't block the use
 
 Another benefit of splitting heavy tasks for browser scripts is that we can show progress indication.
 
-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.
+As mentioned earlier, changes to DOM are painted only after the currently running task is completed, irrespective of how long it takes.
 
 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?
 
@@ -238,7 +238,7 @@ menu.onclick = function() {
 
 ## Macrotasks and Microtasks
 
-Along with *macrotasks*, described in this chapter, there exist *microtasks*, mentioned in the chapter .
+Along with *macrotasks*, described in this chapter, there are *microtasks*, mentioned in the chapter .
 
 Microtasks come solely from our code. They are usually created by promises: an execution of `.then/catch/finally` handler becomes a microtask. Microtasks are used "under the cover" of `await` as well, as it's another form of promise handling.
 
@@ -303,7 +303,7 @@ Here's an example with "counting progress bar", similar to the one shown previou
 
 ## Summary
 
-The more detailed algorithm of the event loop (though still simplified compare to the [specification](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)):
+A more detailed event loop algorithm (though still simplified compared to the [specification](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)):
 
 1. Dequeue and run the oldest task from the *macrotask* queue (e.g. "script").
 2. Execute all *microtasks*:
@@ -316,7 +316,7 @@ The more detailed algorithm of the event loop (though still simplified compare t
 To schedule a new *macrotask*:
 - Use zero delayed `setTimeout(f)`.
 
-That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react on user events and show progress between them.
+That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react to user events and show progress between them.
 
 Also, used in event handlers to schedule an action after the event is fully handled (bubbling done).
 
diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md
index 1ce04b7c4..a0c385924 100644
--- a/3-frames-and-windows/01-popup-windows/article.md
+++ b/3-frames-and-windows/01-popup-windows/article.md
@@ -15,7 +15,7 @@ Also, popups are tricky on mobile devices, that don't show multiple windows simu
 
 Still, there are tasks where popups are still used, e.g. for OAuth authorization (login with Google/Facebook/...), because:
 
-1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe.
+1. A popup is a separate window with its own independent JavaScript environment. So opening a popup from a third-party non-trusted site is safe.
 2. It's very easy to open a popup.
 3. A popup can navigate (change URL) and send messages to the opener window.
 
@@ -237,21 +237,23 @@ There's also `window.onscroll` event.
 
 ## Focus/blur on a window
 
-Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window.  Also there are `focus/blur` events that allow to focus a window and catch the moment when the visitor switches elsewhere.
+Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. And there are also `focus/blur` events that allow to catch the moment when the visitor focuses on a window and switches elsewhere.
 
-In the past evil pages abused those. For instance, look at this code:
+Although, in practice they are severely limited, because in the past evil pages abused them. 
+
+For instance, look at this code:
 
 ```js run
 window.onblur = () => window.focus();
 ```
 
-When a user attempts to switch out of the window (`blur`), it brings it back to focus. The intention is to "lock" the user within the `window`.
+When a user attempts to switch out of the window (`window.onblur`), it brings the window back into focus. The intention is to "lock" the user within the `window`.
 
-So, there are limitations that forbid the code like that. There are many limitations to protect the user from ads and evils pages. They depend on the browser.
+So browsers had to introduce many limitations to forbid the code like that and protect the user from ads and evils pages. They depend on the browser.
 
-For instance, a mobile browser usually ignores that call completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window.
+For instance, a mobile browser usually ignores `window.focus()` completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window.
 
-Still, there are some things that can be done.
+Still, there are some use cases when such calls do work and can be useful.
 
 For instance:
 
diff --git a/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html b/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html
index 478830610..46dd7b5cc 100644
--- a/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html
+++ b/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html
@@ -7,7 +7,7 @@
 
 
 
-  
The iframe below is has sandbox attribute.
+
The iframe below has the sandbox attribute.
diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index 062e1834b..84cf6f1cc 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -97,7 +97,7 @@ That's what the value of `link.href` looks like: blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273 ``` -The browser for each URL generated by `URL.createObjectURL` stores an the URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. +For each URL generated by `URL.createObjectURL` the browser stores a URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects an url. diff --git a/5-network/01-fetch/01-fetch-users/solution.md b/5-network/01-fetch/01-fetch-users/solution.md index b8dfb62a2..3cb88e4ea 100644 --- a/5-network/01-fetch/01-fetch-users/solution.md +++ b/5-network/01-fetch/01-fetch-users/solution.md @@ -3,7 +3,7 @@ To fetch a user we need: `fetch('https://api.github.com/users/USERNAME')`. If the response has status `200`, call `.json()` to read the JS object. -Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting arrray. +Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting array. So here's the code: diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md index 757846287..b612383d7 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -1,61 +1,81 @@ # Fetch: Abort -As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we abort a `fetch`? +As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we cancel an ongoing `fetch`? E.g. if the user actions on our site indicate that the `fetch` isn't needed any more. -There's a special built-in object for such purposes: `AbortController`, that can be used to abort not only `fetch`, but other asynchronous tasks as well. +There's a special built-in object for such purposes: `AbortController`. It can be used to abort not only `fetch`, but other asynchronous tasks as well. -The usage is pretty simple: +The usage is very straightforward: -- Step 1: create a controller: +## The AbortController object - ```js - let controller = new AbortController(); - ``` +Step 1: create a controller: - A controller is an extremely simple object. +```js +let controller = new AbortController(); +``` - - It has a single method `abort()`, and a single property `signal`. - - When `abort()` is called: - - `abort` event triggers on `controller.signal` - - `controller.signal.aborted` property becomes `true`. +A controller is an extremely simple object. - All parties interested to learn about `abort()` call set listeners on `controller.signal` to track it. +- It has a single method `abort()`, +- And a single property `signal` that allows to set event liseners on it. - Like this (without `fetch` yet): +When `abort()` is called: +- `controller.signal` emits the `"abort"` event. +- `controller.signal.aborted` property becomes `true`. - ```js run - let controller = new AbortController(); - let signal = controller.signal; +Generally, we have two parties in the process: +1. The one that performs an cancelable operation, it sets a listener on `controller.signal`. +2. The one one that cancels: it calls `controller.abort()` when needed. - // triggers when controller.abort() is called - signal.addEventListener('abort', () => alert("abort!")); +Here's the full example (without `fetch` yet): - controller.abort(); // abort! +```js run +let controller = new AbortController(); +let signal = controller.signal; + +// The party that performs a cancelable operation +// gets "signal" object +// and sets the listener to trigger when controller.abort() is called +signal.addEventListener('abort', () => alert("abort!")); - alert(signal.aborted); // true - ``` +// The other party, that cancels (at any point later): +controller.abort(); // abort! -- Step 2: pass the `signal` property to `fetch` option: +// The event triggers and signal.aborted becomes true +alert(signal.aborted); // true +``` - ```js - let controller = new AbortController(); - fetch(url, { - signal: controller.signal - }); - ``` +As we can see, `AbortController` is just a means to pass `abort` events when `abort()` is called on it. - The `fetch` method knows how to work with `AbortController`, it listens to `abort` on `signal`. +We could implement same kind of event listening in our code on our own, without `AbortController` object at all. -- Step 3: to abort, call `controller.abort()`: +But what's valuable is that `fetch` knows how to work with `AbortController` object, it's integrated with it. - ```js - controller.abort(); - ``` +## Using with fetch - We're done: `fetch` gets the event from `signal` and aborts the request. +To become able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: -When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`: +```js +let controller = new AbortController(); +fetch(url, { + signal: controller.signal +}); +``` + +The `fetch` method knows how to work with `AbortController`. It will listen to `abort` events on `signal`. + +Now, to to abort, call `controller.abort()`: + +```js +controller.abort(); +``` + +We're done: `fetch` gets the event from `signal` and aborts the request. + +When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`. + +Here's the full example with `fetch` aborted after 1 second: ```js run async // abort in 1 second @@ -75,15 +95,18 @@ try { } ``` -**`AbortController` is scalable, it allows to cancel multiple fetches at once.** +## AbortController is scalable -For instance, here we fetch many `urls` in parallel, and the controller aborts them all: +`AbortController` is scalable, it allows to cancel multiple fetches at once. + +Here's a sketch of code that fetches many `urls` in parallel, and uses a single controller to abort them all: ```js let urls = [...]; // a list of urls to fetch in parallel let controller = new AbortController(); +// an array of fetch promises let fetchJobs = urls.map(url => fetch(url, { signal: controller.signal })); @@ -94,9 +117,9 @@ let results = await Promise.all(fetchJobs); // it aborts all fetches ``` -If we have our own asynchronous jobs, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches. +If we have our own asynchronous tasks, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches. -We just need to listen to its `abort` event: +We just need to listen to its `abort` event in our tasks: ```js let urls = [...]; @@ -118,4 +141,8 @@ let results = await Promise.all([...fetchJobs, ourJob]); // it aborts all fetches and ourJob ``` -So `AbortController` is not only for `fetch`, it's a universal object to abort asynchronous tasks, and `fetch` has built-in integration with it. +## Summary + +- `AbortController` is a simple object that generates `abort` event on it's `signal` property when `abort()` method is called (and also sets `signal.aborted` to `true`). +- `fetch` integrates with it: we pass `signal` property as the option, and then `fetch` listens to it, so it becomes possible to abort the `fetch`. +- We can use `AbortController` in our code. The "call `abort()`" -> "listen to `abort` event" interaction is simple and universal. We can use it even without `fetch`. \ No newline at end of file diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index 36c57aed4..31fb0b8be 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -329,7 +329,7 @@ fetch('http://another.com', { }); ``` -Now `fetch` sends cookies originating from `another.com` without request to that site. +Now `fetch` sends cookies originating from `another.com` with request to that site. If the server agrees to accept the request *with credentials*, it should add a header `Access-Control-Allow-Credentials: true` to the response, in addition to `Access-Control-Allow-Origin`. diff --git a/5-network/06-fetch-api/article.md b/5-network/06-fetch-api/article.md index edd361e5d..413aa47b0 100644 --- a/5-network/06-fetch-api/article.md +++ b/5-network/06-fetch-api/article.md @@ -215,10 +215,10 @@ window.onunload = function() { Normally, when a document is unloaded, all associated network requests are aborted. But `keepalive` option tells the browser to perform the request in background, even after it leaves the page. So this option is essential for our request to succeed. -It has few limitations: +It has a few limitations: - We can't send megabytes: the body limit for `keepalive` requests is 64kb. - - If gather more data, we can send it out regularly in packets, so that there won't be a lot left for the last `onunload` request. - - The limit is for all currently ongoing requests. So we can't cheat it by creating 100 requests, each 64kb. -- We can't handle the server response if the request is made in `onunload`, because the document is already unloaded at that time, functions won't work. - - Usually, the server sends empty response to such requests, so it's not a problem. + - If we need to gather a lot of statistics about the visit, we should send it out regularly in packets, so that there won't be a lot left for the last `onunload` request. + - This limit applies to all `keepalive` requests together. In other words, we can perform multiple `keepalive` requests in parallel, but the sum of their body lengths should not exceed 64kb. +- We can't handle the server response if the document is unloaded. So in our example `fetch` will succeed due to `keepalive`, but subsequent functions won't work. + - In most cases, such as sending out statistics, it's not a problem, as server just accepts the data and usually sends an empty response to such requests. diff --git a/5-network/07-url/article.md b/5-network/07-url/article.md index 591a55af2..c41400451 100644 --- a/5-network/07-url/article.md +++ b/5-network/07-url/article.md @@ -88,7 +88,7 @@ It provides convenient methods for search parameters: - **`delete(name)`** -- remove the parameter by `name`, - **`get(name)`** -- get the parameter by `name`, - **`getAll(name)`** -- get all parameters with the same `name` (that's possible, e.g. `?user=John&user=Pete`), -- **`has(name)`** -- check for the existance of the parameter by `name`, +- **`has(name)`** -- check for the existence of the parameter by `name`, - **`set(name, value)`** -- set/replace the parameter, - **`sort()`** -- sort parameters by name, rarely needed, - ...and it's also iterable, similar to `Map`. diff --git a/5-network/10-long-polling/article.md b/5-network/10-long-polling/article.md index 68eec844e..0020b961a 100644 --- a/5-network/10-long-polling/article.md +++ b/5-network/10-long-polling/article.md @@ -6,7 +6,7 @@ Being very easy to implement, it's also good enough in a lot of cases. ## Regular Polling -The simplest way to get new information from the server is periodic polling. That is, regular requests to the server: "Hello, I'm here, do you have any information for me?". For example, once in 10 seconds. +The simplest way to get new information from the server is periodic polling. That is, regular requests to the server: "Hello, I'm here, do you have any information for me?". For example, once every 10 seconds. 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. @@ -70,9 +70,9 @@ As you can see, `subscribe` function makes a fetch, then waits for the response, ```warn header="Server should be ok with many pending connections" The server architecture must be able to work with many pending connections. -Certain server architectures run a process per connect. For many connections there will be as many processes, and each process takes a lot of memory. So many connections just consume it all. +Certain server architectures run one process per connect. So there will be as many processes as connections, and each process takes a lot of memory. Too many connections just will consume it all. -That's often the case for backends written in PHP, Ruby languages, but technically isn't a language, but rather implementation issue. Most modern language allow to implement a proper backend, but some of them make it easier than the other. +That's often the case for backends written in PHP, Ruby languages, but technically isn't a language issue, but rather implementation one. Most modern language allow to implement a proper backend, but some of them make it easier than others.. Backends written using Node.js usually don't have such problems. ``` diff --git a/5-network/11-websocket/article.md b/5-network/11-websocket/article.md index b3a3b4b03..b374c2b70 100644 --- a/5-network/11-websocket/article.md +++ b/5-network/11-websocket/article.md @@ -177,12 +177,12 @@ A call `socket.send(body)` allows `body` in string or a binary format, including **When we receive the data, text always comes as string. And for binary data, we can choose between `Blob` and `ArrayBuffer` formats.** -That's set by `socket.bufferType` property, it's `"blob"` by default, so binary data comes as `Blob` objects. +That's set by `socket.binaryType` property, it's `"blob"` by default, so binary data comes as `Blob` objects. [Blob](info:blob) is a high-level binary object, it directly integrates with ``, `` and other tags, so that's a sane default. But for binary processing, to access individual data bytes, we can change it to `"arraybuffer"`: ```js -socket.bufferType = "arraybuffer"; +socket.binaryType = "arraybuffer"; socket.onmessage = (event) => { // event.data is either a string (if text) or arraybuffer (if binary) }; diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index 5f18f216b..3bce4cf3a 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -68,7 +68,7 @@ alert(document.cookie); // ...; my%20name=John%20Smith ```warn header="Limitations" There are few limitations: -- The `name=value` pair, after `encodeURIComponent`, should not exceed 4kb. So we can't store anything huge in a cookie. +- The `name=value` pair, after `encodeURIComponent`, should not exceed 4KB. So we can't store anything huge in a cookie. - The total number of cookies per domain is limited to around 20+, the exact limit depends on a browser. ``` @@ -415,7 +415,7 @@ GDPR is not only about cookies, it's about other privacy-related issues too, but `document.cookie` provides access to cookies - write operations modify only cookies mentioned in it. - name/value must be encoded. -- one cookie up to 4kb, 20+ cookies per site (depends on a browser). +- one cookie up to 4KB, 20+ cookies per site (depends on a browser). Cookie options: - `path=/`, by default current path, makes the cookie visible only under that path. diff --git a/6-data-storage/02-localstorage/article.md b/6-data-storage/02-localstorage/article.md index 3ba9aa972..d5d0bbdc6 100644 --- a/6-data-storage/02-localstorage/article.md +++ b/6-data-storage/02-localstorage/article.md @@ -222,7 +222,7 @@ Modern browsers also support [Broadcast channel API](https://developer.mozilla.o Web storage objects `localStorage` and `sessionStorage` allow to store key/value in the browser. - Both `key` and `value` must be strings. -- The limit is 2mb+, depends on the browser. +- The limit is 5mb+, depends on the browser. - They do not expire. - The data is bound to the origin (domain/port/protocol). diff --git a/9-regular-expressions/03-regexp-unicode/article.md b/9-regular-expressions/03-regexp-unicode/article.md index defbe15ab..60d85ff13 100644 --- a/9-regular-expressions/03-regexp-unicode/article.md +++ b/9-regular-expressions/03-regexp-unicode/article.md @@ -33,12 +33,6 @@ Unlike strings, regular expressions have flag `pattern:u` that fixes such proble ## Unicode properties \p{...} -```warn header="Not supported in Firefox and Edge" -Despite being a part of the standard since 2018, unicode properties are not supported in Firefox ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1361876)) and Edge ([bug](https://github.com/Microsoft/ChakraCore/issues/2969)). - -There's [XRegExp](http://xregexp.com) library that provides "extended" regular expressions with cross-browser support for unicode properties. -``` - Every character in Unicode has a lot of properties. They describe what "category" the character belongs to, contain miscellaneous information about it. For instance, if a character has `Letter` property, it means that the character belongs to an alphabet (of any language). And `Number` property means that it's a digit: maybe Arabic or Chinese, and so on.