diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index 9951ba789..32995bf93 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -8,7 +8,6 @@ മിക്ക ഡവലപ്പർമാരും ഡെവലപ്‌മെന്റിനായി Chrome അല്ലെങ്കിൽ Firefox ആയിരിക്കുo ആശ്രയിക്കുന്നത്, കാരണം ആ ബ്രൗസറുകളിൽ മികച്ച ഡവലപ്പർ ടൂൾസ് ഉൾപ്പെടുത്തിയിട്ടുണ്ട്. മറ്റ് ബ്രവ്സറുകളും ഡെവലപ്പർ ടൂൾസ് നൽകുന്നു, ചിലപ്പോൾ ചില പ്രത്യേകതകളും അവ നൽകും, പക്ഷേ സാധാരണയായി Chrome അല്ലെങ്കിൽ Firefox ലോട്ടു അനുകരിക്കുകയാണ് അവ ചെയ്യുന്നത്. അതിനാൽ മിക്ക ഡവലപ്പർമാർക്കും അവർക്ക് "ഇഷ്ടപ്പെട്ട" ഒരു ബ്രൗസർ കാണും കൂടാതെ ഒരു ബ്രൗസറിൽ എന്തെങ്കിലും പ്രശ്നമുള്ളതായി തോന്നുകയാണെങ്കിൽ മറ്റുള്ളവയിലേക്കു മാറുകയോ ചെയ്യും. - ഡവലപ്പർ ടൂൾസ് വളരെ ശക്തമാണ്; അവയ്‌ക്ക് നിരവധി സവിശേഷതകളുണ്ട്. നമുക്കിപ്പോൾ, അവ എങ്ങനെ ഓപ്പൺ ചെയ്യാമെന്നും, തെറ്റുകൾ എങ്ങനെ നോക്കാമെന്നും, javascript command കൾ എങ്ങനെ ടെസ്റ്റ് ചെയ്തു നോക്കാമെന്നും പഠിക്കാം. diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index cf1dd53d2..e81fd343d 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -46,7 +46,7 @@ alert(3 + + 2); ``` -The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so the semicolon is not required. And in this case that works as intended. +The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so a semicolon there would be incorrect. And in this case, that works as intended. **But there are situations where JavaScript "fails" to assume a semicolon where it is really needed.** @@ -56,40 +56,36 @@ Errors which occur in such cases are quite hard to find and fix. If you're curious to see a concrete example of such an error, check this code out: ```js run -[1, 2].forEach(alert) +alert("Hello"); + +[1, 2].forEach(alert); ``` -No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of the code: it shows `1` then `2`. +No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of running the code: it shows `Hello`, then `1`, then `2`. -Now, let's add an `alert` before the code and *not* finish it with a semicolon: +Now let's remove the semicolon after the `alert`: ```js run no-beautify -alert("There will be an error") +alert("Hello") -[1, 2].forEach(alert) +[1, 2].forEach(alert); ``` -Now if we run the code, only the first `alert` is shown and then we have an error! - -But everything is fine again if we add a semicolon after `alert`: -```js run -alert("All fine now"); +The difference compared to the code above is only one character: the semicolon at the end of the first line is gone. -[1, 2].forEach(alert) -``` +If we run this code, only the first `Hello` shows (and there's an error, you may need to open the console to see it). There are no numbers any more. -Now we have the "All fine now" message followed by `1` and `2`. +That's because JavaScript does not assume a semicolon before square brackets `[...]`. So, the code in the last example is treated as a single statement. - -The error in the no-semicolon variant occurs because JavaScript does not assume a semicolon before square brackets `[...]`. - -So, because the semicolon is not auto-inserted, the code in the first example is treated as a single statement. Here's how the engine sees it: +Here's how the engine sees it: ```js run no-beautify -alert("There will be an error")[1, 2].forEach(alert) +alert("Hello")[1, 2].forEach(alert); ``` -But it should be two separate statements, not one. Such a merging in this case is just wrong, hence the error. This can happen in other situations. +Looks weird, right? Such merging in this case is just wrong. We need to put a semicolon after `alert` for the code to work correctly. + +This can happen in other situations also. ```` We recommend putting semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again -- *it is possible* to leave out semicolons most of the time. But it's safer -- especially for a beginner -- to use them. diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 661760005..9fba1d8ad 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -64,7 +64,7 @@ Special numeric values formally belong to the "number" type. Of course they are We'll see more about working with numbers in the chapter . -## BigInt +## BigInt [#bigint-type] 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. diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index 9a8411fbe..dfd061cb6 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -9,7 +9,6 @@ true + false = 1 "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN -7 / 0 = Infinity " -9 " + 5 = " -9 5" // (3) " -9 " - 5 = -14 // (4) null + 1 = 1 // (5) diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md index 930c71514..068420c7d 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md @@ -16,7 +16,6 @@ true + false "$" + 4 + 5 "4" - 2 "4px" - 2 -7 / 0 " -9 " + 5 " -9 " - 5 null + 1 diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index a4958b878..decf5bcfc 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -56,17 +56,21 @@ alert( 8 % 3 ); // 2, a remainder of 8 divided by 3 ### Exponentiation ** -The exponentiation operator `a ** b` multiplies `a` by itself `b` times. +The exponentiation operator `a ** b` raises `a` to the power of `b`. + +In school maths, we write that as ab. For instance: ```js run -alert( 2 ** 2 ); // 4 (2 multiplied by itself 2 times) -alert( 2 ** 3 ); // 8 (2 * 2 * 2, 3 times) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2, 4 times) +alert( 2 ** 2 ); // 2² = 4 +alert( 2 ** 3 ); // 2³ = 8 +alert( 2 ** 4 ); // 2⁴ = 16 ``` -Mathematically, the exponentiation is defined for non-integer numbers as well. For example, a square root is an exponentiation by `1/2`: +Just like in maths, the exponentiation operator is defined for non-integer numbers as well. + +For example, a square root is an exponentiation by ½: ```js run alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root) diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index a4d943245..4305584fa 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md @@ -6,7 +6,7 @@ importance: 2 Using the `if..else` construct, write the code which asks: 'What is the "official" name of JavaScript?' -If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "Didn't know? ECMAScript!" +If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "You don't know? ECMAScript!" ![](ifelse_task2.svg) 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 caa8cdfaf..97f5d738a 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -1,6 +1,6 @@ # Logical operators -There are three logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT). +There are four logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT), `??` (Nullish Coalescing). Here we cover the first three, the `??` operator is in the next article. Although they are called "logical", they can be applied to values of any type, not only boolean. Their result can also be of any type. @@ -64,7 +64,7 @@ if (hour < 10 || hour > 18 || isWeekend) { } ``` -## OR "||" finds the first truthy value +## OR "||" finds the first truthy value [#or-finds-the-first-truthy-value] The logic described above is somewhat classical. Now, let's bring in the "extra" features of JavaScript. 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 55e0c2067..b84dff892 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 @@ -2,15 +2,14 @@ [recent browser="new"] -Here, in this article, we'll say that an expression is "defined" when it's neither `null` nor `undefined`. - The nullish coalescing operator is written as two question marks `??`. +As it treats `null` and `undefined` similarly, we'll use a special term here, in this article. We'll say that an expression is "defined" when it's neither `null` nor `undefined`. + The result of `a ?? b` is: - if `a` is defined, then `a`, - if `a` isn't defined, then `b`. - In other words, `??` returns the first argument if it's not `null/undefined`. Otherwise, the second one. The nullish coalescing operator isn't anything completely new. It's just a nice syntax to get the first "defined" value of the two. @@ -21,29 +20,31 @@ We can rewrite `result = a ?? b` using the operators that we already know, like result = (a !== null && a !== undefined) ? a : b; ``` +Now it should be absolutely clear what `??` does. Let's see where it helps. + The common use case for `??` is to provide a default value for a potentially undefined variable. -For example, here we show `Anonymous` if `user` isn't defined: +For example, here we show `user` if defined, otherwise `Anonymous`: ```js run let user; -alert(user ?? "Anonymous"); // Anonymous +alert(user ?? "Anonymous"); // Anonymous (user not defined) ``` -Of course, if `user` had any value except `null/undefined`, then we would see it instead: +Here's the example with `user` assigned to a name: ```js run let user = "John"; -alert(user ?? "Anonymous"); // John +alert(user ?? "Anonymous"); // John (user defined) ``` We can also use a sequence of `??` to select the first value from a list that isn't `null/undefined`. -Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be undefined, if the user decided not to enter a value. +Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be not defined, if the user decided not to enter a value. -We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are undefined. +We'd like to display the user name using one of these variables, or show "Anonymous" if all of them aren't defined. Let's use the `??` operator for that: @@ -75,7 +76,7 @@ alert(firstName || lastName || nickName || "Anonymous"); // Supercoder */!* ``` -The OR `||` operator exists since the beginning of JavaScript, so developers were using it for such purposes for a long time. +Historically, the OR `||` operator was there first. It exists since the beginning of JavaScript, so developers were using it for such purposes for a long time. On the other hand, the nullish coalescing operator `??` was added to JavaScript only recently, and the reason for that was that people weren't quite happy with `||`. @@ -96,16 +97,18 @@ alert(height || 100); // 100 alert(height ?? 100); // 0 ``` -- The `height || 100` checks `height` for being a falsy value, and it really is. - - so the result is the second argument, `100`. +- The `height || 100` checks `height` for being a falsy value, and it's `0`, falsy indeed. + - so the result of `||` is the second argument, `100`. - The `height ?? 100` checks `height` for being `null/undefined`, and it's not, - so the result is `height` "as is", that is `0`. -If the zero height is a valid value, that shouldn't be replaced with the default, then `??` does just the right thing. +In practice, the zero height is often a valid value, that shouldn't be replaced with the default. So `??` does just the right thing. ## Precedence -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 before `=` and `?`, but after most other operations, such as `+`, `*`. +The precedence of the `??` operator is about the same as `||`, just a bit lower. It equals `5` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table), while `||` is `6`. + +That means that, just like `||`, the nullish coalescing operator `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`. So if we'd like to choose a value with `??` in an expression with other operators, consider adding parentheses: @@ -139,7 +142,7 @@ The code below triggers a syntax error: let x = 1 && 2 ?? 3; // Syntax error ``` -The limitation is surely debatable, but it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch to `??` from `||`. +The limitation is surely debatable, it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch from `||` to `??`. Use explicit parentheses to work around it: diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md index b3e3953b8..2579b647d 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -318,7 +318,7 @@ alert('Done!'); We need a way to stop the process if the user cancels the input. -The ordinary `break` after `input` would only break the inner loop. That's not sufficient--labels, come to the rescue! +The ordinary `break` after `input` would only break the inner loop. That's not sufficient -- labels, come to the rescue! A *label* is an identifier with a colon before a loop: ```js @@ -363,12 +363,23 @@ Labels do not allow us to jump into an arbitrary place in the code. For example, it is impossible to do this: ```js -break label; // doesn't jumps to the label below +break label; // jump to the label below (doesn't work) label: for (...) ``` -A call to `break/continue` is only possible from inside a loop and the label must be somewhere above the directive. +A `break` directive must be inside a code block. Technically, any labelled code block will do, e.g.: +```js +label: { + // ... + break label; // works + // ... +} +``` + +...Although, 99.9% of the time `break` used is inside loops, as we've seen in the examples above. + +A `continue` is only possible from inside a loop. ```` ## Summary diff --git a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md index c8ee9618f..e48502642 100644 --- a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md +++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md @@ -14,4 +14,4 @@ function checkAge(age) { } ``` -Note that the parentheses around `age > 18` are not required here. They exist for better readabilty. +Note that the parentheses around `age > 18` are not required here. They exist for better readability. diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md index b12d0b9e7..ac6f1f47c 100644 --- a/1-js/02-first-steps/15-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -20,10 +20,10 @@ function showMessage() { } ``` -The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above) and finally the code of the function, also named "the function body", between curly braces. +The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above, we'll see examples later) and finally the code of the function, also named "the function body", between curly braces. ```js -function name(parameters) { +function name(parameter1, parameter2, ... parameterN) { ...body... } ``` @@ -137,26 +137,23 @@ It's a good practice to minimize the use of global variables. Modern code has fe ## Parameters -We can pass arbitrary data to functions using parameters (also called *function arguments*) . +We can pass arbitrary data to functions using parameters. In the example below, the function has two parameters: `from` and `text`. ```js run -function showMessage(*!*from, text*/!*) { // arguments: from, text +function showMessage(*!*from, text*/!*) { // parameters: from, text alert(from + ': ' + text); } -*!* -showMessage('Ann', 'Hello!'); // Ann: Hello! (*) -showMessage('Ann', "What's up?"); // Ann: What's up? (**) -*/!* +*!*showMessage('Ann', 'Hello!');*/!* // Ann: Hello! (*) +*!*showMessage('Ann', "What's up?");*/!* // Ann: What's up? (**) ``` When the function is called in lines `(*)` and `(**)`, the given values are copied to local variables `from` and `text`. Then the function uses them. Here's one more example: we have a variable `from` and pass it to the function. Please note: the function changes `from`, but the change is not seen outside, because a function always gets a copy of the value: - ```js run function showMessage(from, text) { @@ -175,9 +172,21 @@ showMessage(from, "Hello"); // *Ann*: Hello alert( from ); // Ann ``` +When a value is passed as a function parameter, it's also called an *argument*. + +In other words, to put these terms straight: + +- A parameter is the variable listed inside the parentheses in the function declaration (it's a declaration time term) +- An argument is the value that is passed to the function when it is called (it's a call time term). + +We declare functions listing their parameters, then call them passing arguments. + +In the example above, one might say: "the function `sayMessage` is declared with two parameters, then called with two arguments: `from` and `"Hello"`". + + ## Default values -If a parameter is not provided, then its value becomes `undefined`. +If a function is called, but an argument is not provided, then the corresponding value becomes `undefined`. For instance, the aforementioned function `showMessage(from, text)` can be called with a single argument: @@ -185,9 +194,9 @@ For instance, the aforementioned function `showMessage(from, text)` can be calle showMessage("Ann"); ``` -That's not an error. Such a call would output `"*Ann*: undefined"`. There's no `text`, so it's assumed that `text === undefined`. +That's not an error. Such a call would output `"*Ann*: undefined"`. As the value for `text` isn't passed, it becomes `undefined`. -If we want to use a "default" `text` in this case, then we can specify it after `=`: +We can specify the so-called "default" (to use if omitted) value for a parameter in the function declaration, using `=`: ```js run function showMessage(from, *!*text = "no text given"*/!*) { @@ -211,19 +220,23 @@ function showMessage(from, text = anotherFunction()) { ```smart header="Evaluation of default parameters" In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. -In the example above, `anotherFunction()` is called every time `showMessage()` is called without the `text` parameter. +In the example above, `anotherFunction()` isn't called at all, if the `text` parameter is provided. + +On the other hand, it's independently called every time when `text` is missing. ``` ### Alternative default parameters -Sometimes it makes sense to set default values for parameters not in the function declaration, but at a later stage, during its execution. +Sometimes it makes sense to assign default values for parameters not in the function declaration, but at a later stage. -To check for an omitted parameter, we can compare it with `undefined`: +We can check if the parameter is passed during the function execution, by comparing it with `undefined`: ```js run function showMessage(text) { + // ... + *!* - if (text === undefined) { + if (text === undefined) { // if the parameter is missing text = 'empty message'; } */!* @@ -234,21 +247,21 @@ function showMessage(text) { showMessage(); // empty message ``` -...Or we could use the `||` operator: +...Or we could use the `??` operator: ```js -// if text parameter is omitted or "" is passed, set it to 'empty' function showMessage(text) { + // if text is undefined or otherwise falsy, set it to 'empty' text = text || 'empty'; ... } ``` -Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when falsy values, such as `0`, are considered regular: +Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when most falsy values, such as `0`, should be considered "normal": ```js run -// if there's no "count" parameter, show "unknown" function showCount(count) { + // if count is undefined or null, show "unknown" alert(count ?? "unknown"); } @@ -411,7 +424,7 @@ Functions that are used *very often* sometimes have ultrashort names. For example, the [jQuery](http://jquery.com) framework defines a function with `$`. The [Lodash](http://lodash.com/) library has its core function named `_`. -These are exceptions. Generally functions names should be concise and descriptive. +These are exceptions. Generally function names should be concise and descriptive. ``` ## Functions == Comments diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md index 3ea112473..041db18bc 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md @@ -1,7 +1,7 @@ ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md index 2f44db27e..e18c08a83 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md @@ -5,7 +5,7 @@ Replace Function Expressions with arrow functions in the code below: ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/17-arrow-functions-basics/article.md b/1-js/02-first-steps/17-arrow-functions-basics/article.md index e0fb5bda5..1b6da9bfe 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/article.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/article.md @@ -5,7 +5,7 @@ There's another very simple and concise syntax for creating functions, that's of It's called "arrow functions", because it looks like this: ```js -let func = (arg1, arg2, ...argN) => expression +let func = (arg1, arg2, ..., argN) => expression ``` ...This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result. @@ -13,7 +13,7 @@ let func = (arg1, arg2, ...argN) => expression In other words, it's the shorter version of: ```js -let func = function(arg1, arg2, ...argN) { +let func = function(arg1, arg2, ..., argN) { return expression; }; ``` diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md index 8c52735bf..cf90f5492 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,4 +1,4 @@ -# Debugging in Chrome +# Debugging in the browser Before writing more complex code, let's talk about debugging. diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 68ffcae4d..9037b484d 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -2,7 +2,7 @@ Automated testing will be used in further tasks, and it's also widely used in real projects. -## Why we need tests? +## Why do we need tests? When we write a function, we can usually imagine what it should do: which parameters give which results. diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index af38faad2..7efaf8677 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -48,7 +48,7 @@ Modern project build systems, such as [webpack](http://webpack.github.io/), prov New language features may include not only syntax constructs and operators, but also built-in functions. -For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23) = 1`. +For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23)` returns `1`. In some (very outdated) JavaScript engines, there's no `Math.trunc`, so such code will fail. diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md index bf646a382..cafb71cac 100644 --- a/1-js/04-object-basics/02-object-copy/article.md +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -100,7 +100,7 @@ alert( a == b ); // false For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake. -## Cloning and merging, Object.assign +## Cloning and merging, Object.assign [#cloning-and-merging-object-assign] So, copying an object variable creates one more reference to the same object. @@ -186,6 +186,8 @@ let clone = Object.assign({}, user); It copies all properties of `user` into the empty object and returns it. +There are also other methods of cloning an object, e.g. using the [spread syntax](info:rest-parameters-spread) `clone = {...user}`, covered later in the tutorial. + ## Nested cloning Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them? diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md index 2b47873fc..ab4e37340 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md @@ -21,7 +21,7 @@ let ladder = { return this; */!* } -} +}; ladder.up().up().down().up().down().showStep(); // 1 ``` diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md index 0d3c1f392..a36b9ca07 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -81,7 +81,7 @@ user = { // method shorthand looks better, right? user = { *!* - sayHi() { // same as "sayHi: function()" + sayHi() { // same as "sayHi: function(){...}" */!* alert("Hello"); } diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index 5f8709ae1..184b30f86 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -64,13 +64,14 @@ Now if we want to create other users, we can call `new User("Ann")`, `new User(" That's the main purpose of constructors -- to implement reusable object creation code. -Let's note once again -- technically, any function can be used as a constructor. That is: any function can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`. +Let's note once again -- technically, any function (except arrow functions, as they don't have `this`) can be used as a constructor. It can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`. ````smart header="new function() { ... }" -If we have many lines of code all about creation of a single complex object, we can wrap them in constructor function, like this: +If we have many lines of code all about creation of a single complex object, we can wrap them in an immediately called constructor function, like this: ```js -let user = new function() { +// create a function and immediately call it with new +let user = new function() { this.name = "John"; this.isAdmin = false; @@ -80,7 +81,7 @@ let user = new function() { }; ``` -The constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse. +This constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse. ```` ## Constructor mode test: new.target 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 0f1ea9a86..9591dcd6c 100644 --- a/1-js/04-object-basics/07-optional-chaining/article.md +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -166,9 +166,9 @@ userGuest.admin?.(); // nothing (no such method) */!* ``` -Here, in both lines we first use the dot (`user1.admin`) to get `admin` property, because the user object must exist, so it's safe read from it. +Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the user object exists, so it's safe read from it. -Then `?.()` checks the left part: if the admin function exists, then it runs (that's so for `user1`). Otherwise (for `user2`) the evaluation stops without errors. +Then `?.()` checks the left part: if the admin function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors. The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist. 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 36b6c6460..b0605d44f 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -3,7 +3,24 @@ What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`? -In that case, objects are auto-converted to primitives, and then the operation is carried out. +JavaScript doesn't exactly allow to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle an addition (or other operators). + +In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value. + +That's an important limitation, as the result of `obj1 + obj2` can't be another object! + +E.g. we can't make objects representing vectors or matrices (or archievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board". + +So, because we can't do much here, there's no maths with objects in real projects. When it happens, it's usually because of a coding mistake. + +In this chapter we'll cover how an object converts to primitive and how to customize it. + +We have two purposes: + +1. It will allow us to understand what's going on in case of coding mistakes, when such an operation happened accidentally. +2. There are exceptions, where such operations are possible and look good. E.g. subtracting or comparing dates (`Date` objects). We'll come across them later. + +## Conversion rules In the chapter we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it. @@ -11,11 +28,11 @@ In the chapter we've seen the rules for numeric, string 2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates. 3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts. -## ToPrimitive - We can fine-tune string and numeric conversion, using special object methods. -There are three variants of type conversion, so-called "hints", described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): +There are three variants of type conversion, that happen in various situations. + +They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): `"string"` : For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`: @@ -82,11 +99,14 @@ Let's start from the first method. There's a built-in symbol named `Symbol.toPri ```js obj[Symbol.toPrimitive] = function(hint) { - // must return a primitive value + // here goes the code to convert this object to a primitive + // it must return a primitive value // hint = one of "string", "number", "default" }; ``` +If the method `Symbol.toPrimitive` exists, it's used for all hints, and no more methods are needed. + For instance, here `user` object implements it: ```js run @@ -111,12 +131,12 @@ As we can see from the code, `user` becomes a self-descriptive string or a money ## toString/valueOf -Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. +If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: -If there's no `Symbol.toPrimitive` then JavaScript tries to find them and try in the order: +- For the "string" hint: `toString`, and if it doesn't exist, then `valueOf` (so `toString` has the priority for stirng conversions). +- For other hints: `valueOf`, and if it doesn't exist, then `toString` (so `valueOf` has the priority for maths). -- `toString -> valueOf` for "string" hint. -- `valueOf -> toString` otherwise. +Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. These methods must return a primitive value. If `toString` or `valueOf` returns an object, then it's ignored (same as if there were no method). @@ -136,9 +156,9 @@ alert(user.valueOf() === user); // true So if we try to use an object as a string, like in an `alert` or so, then by default we see `[object Object]`. -And the default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. +The default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. -Let's implement these methods. +Let's implement these methods to customize the conversion. For instance, here `user` does the same as above using a combination of `toString` and `valueOf` instead of `Symbol.toPrimitive`: @@ -183,7 +203,7 @@ alert(user + 500); // toString -> John500 In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions. -## Return types +### A conversion can return any primitive type The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive. @@ -252,4 +272,6 @@ The conversion algorithm is: 3. Otherwise if hint is `"number"` or `"default"` - try `obj.valueOf()` and `obj.toString()`, whatever exists. -In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for all conversions that return a "human-readable" representation of an object, for logging or debugging purposes. +In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes. + +As for math operations, JavaScript doesn't provide a way to "override" them using methods, so real life projects rarely use them on objects. \ No newline at end of file diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index 6c13acda6..552c3b3b6 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -39,7 +39,7 @@ Objects are "heavier" than primitives. They require additional resources to supp Here's the paradox faced by the creator of JavaScript: -- There are many things one would want to do with a primitive like a string or a number. It would be great to access them as methods. +- There are many things one would want to do with a primitive like a string or a number. It would be great to access them using methods. - Primitives must be as fast and lightweight as possible. The solution looks a little bit awkward, but here it is: diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index e9a02e634..6fd513f43 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -160,7 +160,7 @@ There are two ways to do so: ```js run let num = 1.23456; - alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 + alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 ``` 2. The method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rounds the number to `n` digits after the point and returns a string representation of the result. diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index 4b8e8aa8a..3b07eccad 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -526,9 +526,9 @@ Now it becomes obvious why `a > Z`. The characters are compared by their numeric code. The greater code means that the character is greater. The code for `a` (97) is greater than the code for `Z` (90). - All lowercase letters go after uppercase letters because their codes are greater. -- Some letters like `Ö` stand apart from the main alphabet. Here, it's code is greater than anything from `a` to `z`. +- Some letters like `Ö` stand apart from the main alphabet. Here, its code is greater than anything from `a` to `z`. -### Correct comparisons +### Correct comparisons [#correct-comparisons] The "right" algorithm to do string comparisons is more complex than it may seem, because alphabets are different for different languages. diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index d722341bb..a86dead64 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -379,9 +379,7 @@ alert( arr[0] ); // undefined! no elements. alert( arr.length ); // length 2 ``` -In the code above, `new Array(number)` has all elements `undefined`. - -To evade such surprises, we usually use square brackets, unless we really know what we're doing. +To avoid such surprises, we usually use square brackets, unless we really know what we're doing. ## Multidimensional arrays diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md index 6a4c20baf..e2147ccfa 100644 --- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md @@ -25,7 +25,7 @@ messages.shift(); // now readMessages has 1 element (technically memory may be cleaned later) ``` -The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it. +The `WeakSet` allows to store a set of messages and easily check for the existence of a message in it. It cleans up itself automatically. The tradeoff is that we can't iterate over it, can't get "all read messages" from it directly. But we can do it by iterating over all messages and filtering those that are in the set. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 5adbced20..8d5a86981 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,6 +1,6 @@ # WeakMap and WeakSet -As we know from the chapter , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). +As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. For instance: ```js diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index b633dc274..28c48a533 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -99,4 +99,4 @@ let doublePrices = Object.fromEntries( alert(doublePrices.meat); // 8 ``` -It may look difficult from the first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. +It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. diff --git a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js index 03ddb1528..6538af42b 100644 --- a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js +++ b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js @@ -4,7 +4,7 @@ function topSalary(salaries) { let maxName = null; for(const [name, salary] of Object.entries(salaries)) { - if (max < salary) { + if (maxSalary < salary) { maxSalary = salary; maxName = name; } diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index e9077a595..fb9346aa2 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -151,7 +151,7 @@ We can swap more than two variables this way. ### The rest '...' -Usually, if the array is longer when the list at the left, the "extra" items are omitted. +Usually, if the array is longer than the list at the left, the "extra" items are omitted. For example, here only two items are taken, and the rest is just ignored: diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index a3117806e..ed4e21359 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -348,7 +348,7 @@ let time1 = 0; let time2 = 0; *!* -// run bench(upperSlice) and bench(upperLoop) each 10 times alternating +// run bench(diffSubtract) and bench(diffGetTime) each 10 times alternating for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md index 1f139d7a4..c63fe70cd 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -225,7 +225,7 @@ But there's a subtle difference between `Array.from(obj)` and `[...obj]`: So, for the task of turning something into an array, `Array.from` tends to be more universal. -## Get a new copy of an array/object +## Copy an array/object Remember when we talked about `Object.assign()` [in the past](info:object-copy#cloning-and-merging-object-assign)? @@ -233,8 +233,11 @@ It is possible to do the same thing with the spread syntax. ```js run let arr = [1, 2, 3]; + +*!* let arrCopy = [...arr]; // spread the array into a list of parameters // then put the result into a new array +*/!* // do the arrays have the same contents? alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true @@ -252,8 +255,11 @@ Note that it is possible to do the same thing to make a copy of an object: ```js run let obj = { a: 1, b: 2, c: 3 }; + +*!* let objCopy = { ...obj }; // spread the object into a list of parameters // then return the result in a new object +*/!* // do the objects have the same contents? alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true @@ -267,7 +273,7 @@ alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4} alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3} ``` -This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj);` or for an array `let arrCopy = Object.assign([], arr);` so we prefer to use it whenever we can. +This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj)` or for an array `let arrCopy = Object.assign([], arr)` so we prefer to use it whenever we can. ## Summary diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md index 3dbefb521..9d99aa717 100644 --- a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md @@ -88,11 +88,11 @@ Let's examine what exactly happens inside `makeArmy`, and the solution will beco Here `let j = i` declares an "iteration-local" variable `j` and copies `i` into it. Primitives are copied "by value", so we actually get an independent copy of `i`, belonging to the current loop iteration. - The shooters work correctly, because the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration: + The shooters work correctly, because the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration: ![](lexenv-makearmy-while-fixed.svg) - Such problem could also be avoided if we used `for` in the beginning, like this: + Such a problem could also be avoided if we used `for` in the beginning, like this: ```js run demo function makeArmy() { 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 346e4060a..b16b35290 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 @@ -27,7 +27,7 @@ The code above demonstrates it. function func() { *!* // the local variable x is known to the engine from the beginning of the function, - // but "unitialized" (unusable) until let ("dead zone") + // but "uninitialized" (unusable) until let ("dead zone") // hence the error */!* diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 3a6a7e4e5..199887063 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -146,7 +146,7 @@ Despite being simple, slightly modified variants of that code have practical use How does this work? If we create multiple counters, will they be independent? What's going on with the variables here? -Undestanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. +Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. ## Lexical Environment 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 ae4883c48..40131e339 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -27,7 +27,7 @@ alert(window.gVar); // 5 (became a property of the global object) The same effect have function declarations (statements with `function` keyword in the main code flow, not function expressions). -Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such thing doesn't happen. +Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such a thing doesn't happen. If we used `let` instead, such thing wouldn't happen: diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md index 981da23cd..e97039f72 100644 --- a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md @@ -52,4 +52,4 @@ function f(b) { } ``` -This `f` will be used in the next call, again return itself, so many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. +This `f` will be used in the next call, again return itself, as many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md index e93090290..12342f45a 100644 --- a/1-js/06-advanced-functions/06-function-object/article.md +++ b/1-js/06-advanced-functions/06-function-object/article.md @@ -326,7 +326,7 @@ welcome(); // Hello, Guest (nested call works) Now it works, because the name `"func"` is function-local. It is not taken from outside (and not visible there). The specification guarantees that it will always reference the current function. -The outer code still has it's variable `sayHi` or `welcome`. And `func` is an "internal function name", how the function can call itself internally. +The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", how the function can call itself internally. ```smart header="There's no such thing for Function Declaration" The "internal name" feature described here is only available for Function Expressions, not for Function Declarations. For Function Declarations, there is no syntax for adding an "internal" name. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index cf851f771..6950664be 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -12,11 +12,10 @@ function throttle(func, ms) { savedThis = this; return; } + isThrottled = true; func.apply(this, arguments); // (1) - isThrottled = true; - setTimeout(function() { isThrottled = false; // (3) if (savedArgs) { diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index d0dda4df1..c5d785493 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -36,11 +36,11 @@ function cachingDecorator(func) { slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1) is cached -alert( "Again: " + slow(1) ); // the same +alert( slow(1) ); // slow(1) is cached and the result returned +alert( "Again: " + slow(1) ); // slow(1) result returned from cache -alert( slow(2) ); // slow(2) is cached -alert( "Again: " + slow(2) ); // the same as the previous line +alert( slow(2) ); // slow(2) is cached and the result returned +alert( "Again: " + slow(2) ); // slow(2) result returned from cache ``` In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior. @@ -301,18 +301,18 @@ The only syntax difference between `call` and `apply` is that `call` expects a l So these two calls are almost equivalent: ```js -func.call(context, ...args); // pass an array as list with spread syntax -func.apply(context, args); // is same as using call +func.call(context, ...args); +func.apply(context, args); ``` -There's only a subtle difference: +They perform the same call of `func` with given context and arguments. + +There's only a subtle difference regarding `args`: - The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. - The `apply` accepts only *array-like* `args`. -So, where we expect an iterable, `call` works, and where we expect an array-like, `apply` works. - -And for objects that are both iterable and array-like, like a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. +...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. Passing all arguments along with the context to another function is called *call forwarding*. diff --git a/1-js/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md index f5caeaece..8730277ad 100644 --- a/1-js/06-advanced-functions/12-arrow-functions/article.md +++ b/1-js/06-advanced-functions/12-arrow-functions/article.md @@ -52,7 +52,7 @@ let group = { *!* this.students.forEach(function(student) { // Error: Cannot read property 'title' of undefined - alert(this.title + ': ' + student) + alert(this.title + ': ' + student); }); */!* } @@ -87,7 +87,7 @@ For instance, `defer(f, ms)` gets a function and returns a wrapper around it tha ```js run function defer(f, ms) { return function() { - setTimeout(() => f.apply(this, arguments), ms) + setTimeout(() => f.apply(this, arguments), ms); }; } diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index cfec05689..02236af75 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -12,7 +12,7 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named ![prototype](object-prototype-empty.svg) -When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. +When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. The property `[[Prototype]]` is internal and hidden, but there are many ways to set it. diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md index 0073e252e..372d50dd6 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md @@ -38,7 +38,12 @@ Why `user2.name` is `undefined`? Here's how `new user.constructor('Pete')` works: 1. First, it looks for `constructor` in `user`. Nothing. -2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing. -3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. +2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!). +3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`. +4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used. -At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object, similar to `let user2 = {}`, that's what we have in `user2` after all. +Finally, at the end, we have `let user2 = new Object('Pete')`. + +Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`. + +(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all). \ No newline at end of file diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 318cf2891..d19b9ca9a 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -110,7 +110,7 @@ alert(typeof User); // function alert(User === User.prototype.constructor); // true // The methods are in User.prototype, e.g: -alert(User.prototype.sayHi); // alert(this.name); +alert(User.prototype.sayHi); // the code of the sayHi method // there are exactly two methods in the prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi @@ -144,7 +144,7 @@ The result of this definition is about the same. So, there are indeed reasons wh Still, there are important differences. -1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually. +1. First, a function created by `class` is labelled by a special internal property `[[IsClassConstructor]]: true`. So it's not entirely the same as creating it manually. The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with `new`: diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index ab08f2ded..c75ec257f 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -125,7 +125,7 @@ That is the same as a direct assignment to `Article`: Article.publisher = "Ilya Kantor"; ``` -## Inheritance of static properties and methods +## Inheritance of static properties and methods [#statics-and-inheritance] Static properties and methods are inherited. diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 2d47464c4..e26d0ec81 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -192,7 +192,7 @@ There's a finished JavaScript proposal, almost in the standard, that provides la Privates should start with `#`. They are only accessible from inside the class. -For instance, here's a private `#waterLimit` property and the water-checking private method `#checkWater`: +For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`: ```js run class CoffeeMachine { diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md index 303431d6d..ec0dabc9a 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md @@ -1,8 +1,8 @@ The difference becomes obvious when we look at the code inside a function. -The behavior is different if there's a "jump out" of `try..catch`. +The behavior is different if there's a "jump out" of `try...catch`. -For instance, when there's a `return` inside `try..catch`. The `finally` clause works in case of *any* exit from `try..catch`, even via the `return` statement: right after `try..catch` is done, but before the calling code gets the control. +For instance, when there's a `return` inside `try...catch`. The `finally` clause works in case of *any* exit from `try...catch`, even via the `return` statement: right after `try...catch` is done, but before the calling code gets the control. ```js run function f() { @@ -11,7 +11,7 @@ function f() { *!* return "result"; */!* - } catch (e) { + } catch (err) { /// ... } finally { alert('cleanup!'); @@ -28,11 +28,11 @@ function f() { try { alert('start'); throw new Error("an error"); - } catch (e) { + } catch (err) { // ... if("can't handle the error") { *!* - throw e; + throw err; */!* } diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md index c573cc232..b6dc81326 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md @@ -6,12 +6,12 @@ importance: 5 Compare the two code fragments. -1. The first one uses `finally` to execute the code after `try..catch`: +1. The first one uses `finally` to execute the code after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } finally { *!* @@ -19,12 +19,12 @@ Compare the two code fragments. */!* } ``` -2. The second fragment puts the cleaning right after `try..catch`: +2. The second fragment puts the cleaning right after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } 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 3a2dc4ed4..a928da289 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -1,14 +1,14 @@ -# Error handling, "try..catch" +# Error handling, "try...catch" No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons. Usually, a script "dies" (immediately stops) in case of an error, printing it to console. -But there's a syntax construct `try..catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. +But there's a syntax construct `try...catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. -## The "try..catch" syntax +## The "try...catch" syntax -The `try..catch` construct has two main blocks: `try`, and then `catch`: +The `try...catch` construct has two main blocks: `try`, and then `catch`: ```js try { @@ -25,12 +25,12 @@ try { It works like this: 1. First, the code in `try {...}` is executed. -2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. -3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch(err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. +2. If there were no errors, then `catch (err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. +3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch (err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. ![](try-catch-flow.svg) -So, an error inside the `try {…}` block does not kill the script -- we have a chance to handle it in `catch`. +So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`. Let's look at some examples. @@ -45,7 +45,7 @@ Let's look at some examples. alert('End of try runs'); // *!*(2) <--*/!* - } catch(err) { + } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) @@ -64,7 +64,7 @@ Let's look at some examples. alert('End of try (never reached)'); // (2) - } catch(err) { + } catch (err) { alert(`Error has occurred!`); // *!*(3) <--*/!* @@ -72,45 +72,45 @@ Let's look at some examples. ``` -````warn header="`try..catch` only works for runtime errors" -For `try..catch` to work, the code must be runnable. In other words, it should be valid JavaScript. +````warn header="`try...catch` only works for runtime errors" +For `try...catch` to work, the code must be runnable. In other words, it should be valid JavaScript. It won't work if the code is syntactically wrong, for instance it has unmatched curly braces: ```js run try { {{{{{{{{{{{{ -} catch(e) { +} catch (err) { alert("The engine can't understand this code, it's invalid"); } ``` The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. -So, `try..catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". +So, `try...catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". ```` -````warn header="`try..catch` works synchronously" -If an exception happens in "scheduled" code, like in `setTimeout`, then `try..catch` won't catch it: +````warn header="`try...catch` works synchronously" +If an exception happens in "scheduled" code, like in `setTimeout`, then `try...catch` won't catch it: ```js run try { setTimeout(function() { noSuchVariable; // script will die here }, 1000); -} catch (e) { +} catch (err) { alert( "won't work" ); } ``` -That's because the function itself is executed later, when the engine has already left the `try..catch` construct. +That's because the function itself is executed later, when the engine has already left the `try...catch` construct. -To catch an exception inside a scheduled function, `try..catch` must be inside that function: +To catch an exception inside a scheduled function, `try...catch` must be inside that function: ```js run setTimeout(function() { try { - noSuchVariable; // try..catch handles the error! + noSuchVariable; // try...catch handles the error! } catch { alert( "error is caught here!" ); } @@ -125,7 +125,7 @@ When an error occurs, JavaScript generates an object containing the details abou ```js try { // ... -} catch(err) { // <-- the "error object", could use another word instead of err +} catch (err) { // <-- the "error object", could use another word instead of err // ... } ``` @@ -150,7 +150,7 @@ try { *!* lalala; // error, variable is not defined! */!* -} catch(err) { +} catch (err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...call stack) @@ -175,9 +175,9 @@ try { } ``` -## Using "try..catch" +## Using "try...catch" -Let's explore a real-life use case of `try..catch`. +Let's explore a real-life use case of `try...catch`. As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) method to read JSON-encoded values. @@ -205,7 +205,7 @@ Should we be satisfied with that? Of course not! This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message. -Let's use `try..catch` to handle the error: +Let's use `try...catch` to handle the error: ```js run let json = "{ bad json }"; @@ -217,12 +217,12 @@ try { */!* alert( user.name ); // doesn't work -} catch (e) { +} catch (err) { *!* // ...the execution jumps here alert( "Our apologies, the data has errors, we'll try to request it one more time." ); - alert( e.name ); - alert( e.message ); + alert( err.name ); + alert( err.message ); */!* } ``` @@ -245,7 +245,7 @@ try { alert( user.name ); // no name! */!* -} catch (e) { +} catch (err) { alert( "doesn't execute" ); } ``` @@ -294,11 +294,11 @@ Let's see what kind of error `JSON.parse` generates: ```js run try { JSON.parse("{ bad json o_O }"); -} catch(e) { +} catch (err) { *!* - alert(e.name); // SyntaxError + alert(err.name); // SyntaxError */!* - alert(e.message); // Unexpected token b in JSON at position 2 + alert(err.message); // Unexpected token b in JSON at position 2 } ``` @@ -323,8 +323,8 @@ try { alert( user.name ); -} catch(e) { - alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name +} catch (err) { + alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name } ``` @@ -334,7 +334,7 @@ Now `catch` became a single place for all error handling: both for `JSON.parse` ## Rethrowing -In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing. +In the example above we use `try...catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing. For example: @@ -345,7 +345,7 @@ try { user = JSON.parse(json); // <-- forgot to put "let" before user // ... -} catch(err) { +} catch (err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (no JSON Error actually) } @@ -353,7 +353,7 @@ try { Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a bug may be discovered that leads to terrible hacks. -In our case, `try..catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. +In our case, `try...catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. To avoid such problems, we can employ the "rethrowing" technique. The rule is simple: @@ -362,7 +362,7 @@ To avoid such problems, we can employ the "rethrowing" technique. The rule is si 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. In the `catch (err) {...}` block we analyze the error object `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: @@ -370,7 +370,7 @@ Usually, we can check the error type using the `instanceof` operator: ```js run try { user = { /*...*/ }; -} catch(err) { +} catch (err) { *!* if (err instanceof ReferenceError) { */!* @@ -399,24 +399,24 @@ try { alert( user.name ); -} catch(e) { +} catch (err) { *!* - if (e instanceof SyntaxError) { - alert( "JSON Error: " + e.message ); + if (err instanceof SyntaxError) { + alert( "JSON Error: " + err.message ); } else { - throw e; // rethrow (*) + throw err; // rethrow (*) } */!* } ``` -The error throwing on line `(*)` from inside `catch` block "falls out" of `try..catch` and can be either caught by an outer `try..catch` construct (if it exists), or it kills the script. +The error throwing on line `(*)` from inside `catch` block "falls out" of `try...catch` and can be either caught by an outer `try...catch` construct (if it exists), or it kills the script. So the `catch` block actually handles only errors that it knows how to deal with and "skips" all others. -The example below demonstrates how such errors can be caught by one more level of `try..catch`: +The example below demonstrates how such errors can be caught by one more level of `try...catch`: ```js run function readData() { @@ -427,11 +427,11 @@ function readData() { *!* blabla(); // error! */!* - } catch (e) { + } catch (err) { // ... - if (!(e instanceof SyntaxError)) { + if (!(err instanceof SyntaxError)) { *!* - throw e; // rethrow (don't know how to deal with it) + throw err; // rethrow (don't know how to deal with it) */!* } } @@ -439,20 +439,20 @@ function readData() { try { readData(); -} catch (e) { +} catch (err) { *!* - alert( "External catch got: " + e ); // caught it! + alert( "External catch got: " + err ); // caught it! */!* } ``` -Here `readData` only knows how to handle `SyntaxError`, while the outer `try..catch` knows how to handle everything. +Here `readData` only knows how to handle `SyntaxError`, while the outer `try...catch` knows how to handle everything. -## try..catch..finally +## try...catch...finally Wait, that's not all. -The `try..catch` construct may have one more code clause: `finally`. +The `try...catch` construct may have one more code clause: `finally`. If it exists, it runs in all cases: @@ -464,7 +464,7 @@ The extended syntax looks like this: ```js *!*try*/!* { ... try to execute the code ... -} *!*catch*/!*(e) { +} *!*catch*/!* (err) { ... handle errors ... } *!*finally*/!* { ... execute always ... @@ -477,7 +477,7 @@ Try running this code: try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); -} catch (e) { +} catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); @@ -513,7 +513,7 @@ let start = Date.now(); try { result = fib(num); -} catch (e) { +} catch (err) { result = 0; *!* } finally { @@ -531,14 +531,14 @@ You can check by running the code with entering `35` into `prompt` -- it execute In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases. -```smart header="Variables are local inside `try..catch..finally`" -Please note that `result` and `diff` variables in the code above are declared *before* `try..catch`. +```smart header="Variables are local inside `try...catch...finally`" +Please note that `result` and `diff` variables in the code above are declared *before* `try...catch`. Otherwise, if we declared `let` in `try` block, it would only be visible inside of it. ``` ````smart header="`finally` and `return`" -The `finally` clause works for *any* exit from `try..catch`. That includes an explicit `return`. +The `finally` clause works for *any* exit from `try...catch`. That includes an explicit `return`. In the example below, there's a `return` in `try`. In this case, `finally` is executed just before the control returns to the outer code. @@ -550,7 +550,7 @@ function func() { return 1; */!* - } catch (e) { + } catch (err) { /* ... */ } finally { *!* @@ -563,9 +563,9 @@ alert( func() ); // first works alert from finally, and then this one ``` ```` -````smart header="`try..finally`" +````smart header="`try...finally`" -The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. +The `try...finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. ```js function func() { @@ -586,7 +586,7 @@ In the code above, an error inside `try` always falls out, because there's no `c The information from this section is not a part of the core JavaScript. ``` -Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error or some other terrible thing. +Let's imagine we've got a fatal error outside of `try...catch`, and the script died. Like a programming error or some other terrible thing. Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages), etc. @@ -643,14 +643,14 @@ They work like this: ## Summary -The `try..catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. +The `try...catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. The syntax is: ```js try { // run this code -} catch(err) { +} catch (err) { // if an error happened, then jump here // err is the error object } finally { @@ -658,7 +658,7 @@ try { } ``` -There may be no `catch` section or no `finally`, so shorter constructs `try..catch` and `try..finally` are also valid. +There may be no `catch` section or no `finally`, so shorter constructs `try...catch` and `try...finally` are also valid. Error objects have following properties: @@ -666,10 +666,10 @@ Error objects have following properties: - `name` -- the string with error name (error constructor name). - `stack` (non-standard, but well-supported) -- the stack at the moment of error creation. -If an error object is not needed, we can omit it by using `catch {` instead of `catch(err) {`. +If an error object is not needed, we can omit it by using `catch {` instead of `catch (err) {`. We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter. *Rethrowing* is a very important pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know. -Even if we don't have `try..catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`. +Even if we don't have `try...catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`. diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index cbec7c46b..f2f533220 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -1,10 +1,10 @@ # Promise -Imagine that you're a top singer, and fans ask day and night for your upcoming single. +Imagine that you're a top singer, and fans ask day and night for your upcoming song. To get some relief, you promise to send it to them when it's published. You give your fans a list. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, a fire in the studio, so that you can't publish the song, they will still be notified. -Everyone is happy: you, because the people don't crowd you anymore, and fans, because they won't miss the single. +Everyone is happy: you, because the people don't crowd you anymore, and fans, because they won't miss the song. This is a real-life analogy for things we often have in programming: @@ -28,10 +28,10 @@ Its arguments `resolve` and `reject` are callbacks provided by JavaScript itself When the executor obtains the result, be it soon or late, doesn't matter, it should call one of these callbacks: -- `resolve(value)` — if the job finished successfully, with result `value`. -- `reject(error)` — if an error occurred, `error` is the error object. +- `resolve(value)` — if the job is finished successfully, with result `value`. +- `reject(error)` — if an error has occurred, `error` is the error object. -So to summarize: the executor runs automatically and attempts to perform a job. When it is finished with the attempt it calls `resolve` if it was successful or `reject` if there was an error. +So to summarize: the executor runs automatically and attempts to perform a job. When it is finished with the attempt, it calls `resolve` if it was successful or `reject` if there was an error. The `promise` object returned by the `new Promise` constructor has these internal properties: diff --git a/1-js/11-async/05-promise-api/article.md b/1-js/11-async/05-promise-api/article.md index 38e13397e..2375f1e74 100644 --- a/1-js/11-async/05-promise-api/article.md +++ b/1-js/11-async/05-promise-api/article.md @@ -1,6 +1,6 @@ # Promise API -There are 5 static methods in the `Promise` class. We'll quickly cover their use cases here. +There are 6 static methods in the `Promise` class. We'll quickly cover their use cases here. ## Promise.all diff --git a/1-js/11-async/07-microtask-queue/article.md b/1-js/11-async/07-microtask-queue/article.md index b146c26b8..014dd93c0 100644 --- a/1-js/11-async/07-microtask-queue/article.md +++ b/1-js/11-async/07-microtask-queue/article.md @@ -30,7 +30,7 @@ As stated in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-jo - The queue is first-in-first-out: tasks enqueued first are run first. - Execution of a task is initiated only when nothing else is running. -Or, to say more simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue; they are not executed yet. When the JavaScript engine becomes free from the current code, it takes a task from the queue and executes it. +Or, to put it more simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue; they are not executed yet. When the JavaScript engine becomes free from the current code, it takes a task from the queue and executes it. That's why "code finished" in the example above shows first. @@ -40,7 +40,7 @@ Promise handlers always go through this internal queue. If there's a chain with multiple `.then/catch/finally`, then every one of them is executed asynchronously. That is, it first gets queued, then executed when the current code is complete and previously queued handlers are finished. -**What if the order matters for us? How can we make `code finished` run after `promise done`?** +**What if the order matters for us? How can we make `code finished` appear after `promise done`?** Easy, just put it into the queue with `.then`: diff --git a/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md b/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md index 105948833..aa462dbf7 100644 --- a/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md +++ b/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md @@ -1,5 +1,5 @@ -There are no tricks here. Just replace `.catch` with `try...catch` inside `demoGithubUser` and add `async/await` where needed: +There are no tricks here. Just replace `.catch` with `try..catch` inside `demoGithubUser` and add `async/await` where needed: ```js run class HttpError extends Error { diff --git a/1-js/11-async/08-async-await/article.md b/1-js/11-async/08-async-await/article.md index bde7c72ea..de965d48d 100644 --- a/1-js/11-async/08-async-await/article.md +++ b/1-js/11-async/08-async-await/article.md @@ -69,10 +69,10 @@ The function execution "pauses" at the line `(*)` and resumes when the promise s 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. +It's just a more elegant syntax of getting the promise result than `promise.then`. And, it's easier to read and write. ````warn header="Can't use `await` in regular functions" -If we try to use `await` in non-async function, there would be a syntax error: +If we try to use `await` in a non-async function, there would be a syntax error: ```js run function f() { @@ -83,7 +83,7 @@ function f() { } ``` -We may get this error if we forget to 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 stated earlier, `await` only works inside an `async` function. ```` Let's take the `showAvatar()` example from the chapter and rewrite it using `async/await`: @@ -186,7 +186,7 @@ class Waiter { new Waiter() .wait() - .then(alert); // 1 + .then(alert); // 1 (this is the same as (result => alert(result))) ``` The meaning is the same: it ensures that the returned value is a promise and enables `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 072e10472..d4e9f7861 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 @@ -384,6 +384,8 @@ An example of use (shows commit authors in console): } })(); + +// Note: If you are running this in an external sandbox, you'll need to paste here the function fetchCommits described above ``` That's just what we wanted. diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index 9afe0fdaa..7dc65a769 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -57,7 +57,7 @@ Like this: The browser automatically fetches and evaluates the imported module (and its imports if needed), and then runs the script. -```warn header="Modules work only via HTTP(s), not in local files" +```warn header="Modules work only via HTTP(s), not locally" If you try to open a web-page locally, via `file://` protocol, you'll find that `import/export` directives don't work. Use a local web-server, such as [static-server](https://www.npmjs.com/package/static-server#getting-started) or use the "live server" capability of your editor, such as VS Code [Live Server Extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) to test modules. ``` @@ -69,7 +69,7 @@ There are core features, valid both for browser and server-side JavaScript. ### Always "use strict" -Modules always `use strict`, by default. E.g. assigning to an undeclared variable will give an error. +Modules always work in strict mode. E.g. assigning to an undeclared variable will give an error. ```html run ``` -If we really need to make a window-level global variable, we can explicitly assign it to `window` and access as `window.user`. But that's an exception requiring a good reason. +```smart +In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`. + +Then all scripts will see it, both with `type="module"` and without it. + +That said, making such global variables is frowned upon. Please try to avoid them. +``` ### A module code is evaluated only the first time when imported -If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers. +If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers. + +The one-time evaluation has important consequences, that we should be aware of. -That has important consequences. Let's look at them using examples: +Let's see a couple of examples. First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time: @@ -133,9 +146,11 @@ import `./alert.js`; // Module is evaluated! import `./alert.js`; // (shows nothing) ``` -In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable -- export it. +The second import shows nothing, because the module has already been evaluated. -Now, a more advanced example. +There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above. + +Now, let's consider a deeper example. Let's say, a module exports an object: @@ -160,54 +175,67 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* -// Both 1.js and 2.js imported the same object +// Both 1.js and 2.js reference the same admin object // Changes made in 1.js are visible in 2.js */!* ``` -So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that. +As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`. + +That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that. -Such behavior allows us to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready. +**Such behavior is actually very convenient, because it allows us to *configure* modules.** -For instance, the `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside: +In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it. + +Here's the classical pattern: +1. A module exports some means of configuration, e.g. a configuration object. +2. On the first import we initialize it, write to its properties. The top-level application script may do that. +3. Further imports use the module. + +For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside: ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` -In `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself: +Here, `admin.js` exports the `config` object (initially empty, but may have default properties too). + +Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`: ```js // 📁 init.js -import {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` -Another module can also see `admin.name`: +...Now the module `admin.js` is configured. -```js -// 📁 other.js -import {admin, sayHi} from './admin.js'; +Further importers can call it, and it correctly shows the current user: -alert(admin.name); // *!*Pete*/!* +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta The object `import.meta` contains the information about the current module. -Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML: +Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML: ```html run height=0 ``` diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 497205f42..10e47820f 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -337,13 +337,19 @@ auth/ ... ``` -We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this: +We'd like to expose the package functionality via a single entry point. + +In other words, a person who would like to use our package, should import only from the "main file" `auth/index.js`. + +Like this: ```js import {login, logout} from 'auth/index.js' ``` -The idea is that outsiders, developers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. +The "main file", `auth/index.js` exports all the functionality that we'd like to provide in our package. + +The idea is that outsiders, other programmers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. As the actual exported functionality is scattered among the package, we can import it into `auth/index.js` and export from it: @@ -366,14 +372,16 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo ```js // 📁 auth/index.js -// import login/logout and immediately export them +// re-export login/logout export {login, logout} from './helpers.js'; -// import default as User and export it +// re-export the default export as User export {default as User} from './user.js'; ... ``` +The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. + ### Re-exporting the default export The default export needs separate handling when re-exporting. @@ -420,14 +428,14 @@ You can check yourself by reading them and recalling what they mean: Import: -- Named exports from module: +- Importing named exports: - `import {x [as y], ...} from "module"` -- Default export: +- Importing the default export: - `import x from "module"` - `import {default as x} from "module"` -- Everything: +- Import all: - `import * as obj from "module"` -- Import the module (its code runs), but do not assign it to a variable: +- Import the module (its code runs), but do not assign any of its exports to variables: - `import "module"` We can put `import/export` statements at the top or at the bottom of a script, that doesn't matter. 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 227253436..1ec378059 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -4,7 +4,7 @@ ```warn header="In-depth language feature" This article covers an advanced topic, to understand certain edge-cases better. -It's not important. Many experienced developers live fine without knowing it. Read on if you're want to know how things work under the hood. +It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood. ``` A dynamically evaluated method call can lose `this`. diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index f7123d70d..b5f03098c 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -214,7 +214,7 @@ alert( document.body.previousSibling ); // HTMLHeadElement ## Element-only navigation -Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist. +Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist. But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page. diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md index e127bc0ef..40c75dff3 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md @@ -6,7 +6,7 @@ importance: 5 We have an empty DOM element `elem` and a string `text`. -Which of these 3 commands do exactly the same? +Which of these 3 commands will do exactly the same? 1. `elem.append(document.createTextNode(text))` 2. `elem.innerHTML = text` diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 6b85168b9..3d1f6698f 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,9 +1,9 @@ The HTML in the task is incorrect. That's the reason of the odd thing. -The browser has to fix it automatically. But there may be no text inside the ``: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `
`. +The browser has to fix it automatically. But there may be no text inside the `
`: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
`. Now it's obvious that when we remove the table, it remains. -The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the `
`. +The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
`. The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct. diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md index 43b0a34a7..a57e7e2d9 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/task.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md @@ -10,7 +10,7 @@ For every list item: 1. Ask a user about its content using `prompt`. 2. Create the `
  • ` with it and add it to `
      `. -3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt). +3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry). All elements should be created dynamically. diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index 95a5cd48b..08a2f6576 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -73,6 +73,12 @@ alert('Current scroll from the left: ' + window.pageXOffset); These properties are read-only. +```smart header="Also available as `window` properties `scrollX` and `scrollY`" +For historical reasons, both properties exist, but they are the same: +- `window.pageXOffset` is an alias of `window.scrollX`. +- `window.pageYOffset` is an alias of `window.scrollY`. +``` + ## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] ```warn diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index 6cb1152c1..49ab88be2 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -124,7 +124,7 @@ Let's update our algorithm: ```js // onmousemove - // ball has position:absoute + // ball has position:absolute ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index 6c7e18aca..689301e4d 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -8,11 +8,11 @@ Working with forms will be much more convenient when we learn them. Document forms are members of the special collection `document.forms`. -That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form. +That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form. ```js no-beautify -document.forms.my - the form with name="my" -document.forms[0] - the first form in the document +document.forms.my; // the form with name="my" +document.forms[0]; // the first form in the document ``` When we have a form, then any element is available in the named collection `form.elements`. @@ -36,9 +36,9 @@ For instance: ``` -There may be multiple elements with the same name, that's often the case with radio buttons. +There may be multiple elements with the same name. This is typical with radio buttons and checkboxes. -In that case `form.elements[name]` is a collection, for instance: +In that case, `form.elements[name]` is a *collection*. For instance: ```html run height=40
      @@ -119,7 +119,7 @@ That's easy to see in an example: ``` -That's usually not a problem, because we rarely change names of form elements. +That's usually not a problem, however, because we rarely change names of form elements. ```` @@ -204,7 +204,7 @@ Here is an example of all three methods: ``` -Unlike most other controls, `` allows to select multiple options at once if it has `multiple` attribute. This attribute is rarely used, though. For multiple selected values, use the first way of setting values: add/remove the `selected` property from `
  • ` is in "edit mode", clicks on other cells are ignored. - The table may have many cells. Use event delegation. diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md index fc48c21ff..644d814d9 100644 --- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md @@ -9,4 +9,5 @@ Focus on the mouse. Then use arrow keys to move it: [demo src="solution"] P.S. Don't put event handlers anywhere except the `#mouse` element. + P.P.S. Don't modify HTML/CSS, the approach should be generic and work with any element. diff --git a/2-ui/4-forms-controls/2-focus-blur/article.md b/2-ui/4-forms-controls/2-focus-blur/article.md index d4348d25b..b866a5e2b 100644 --- a/2-ui/4-forms-controls/2-focus-blur/article.md +++ b/2-ui/4-forms-controls/2-focus-blur/article.md @@ -104,7 +104,7 @@ The best recipe is to be careful when using these events. If we want to track us ``` ## Allow focusing on any element: tabindex -By default many elements do not support focusing. +By default, many elements do not support focusing. The list varies a bit between browsers, but one thing is always correct: `focus/blur` support is guaranteed for elements that a visitor can interact with: ` + ## Selection properties -Similar to a range, a selection has a start, called "anchor", and the end, called "focus". +As said, a selection may in theory contain multiple ranges. We can get these range objects using the method: + +- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. + +Also, there exist properties that often provide better convenience. + +Similar to a range, a selection object has a start, called "anchor", and the end, called "focus". The main selection properties are: @@ -294,36 +352,39 @@ The main selection properties are: - `isCollapsed` -- `true` if selection selects nothing (empty range), or doesn't exist. - `rangeCount` -- count of ranges in the selection, maximum `1` in all browsers except Firefox. -````smart header="Usually, the selection end `focusNode` is after its start `anchorNode`, but it's not always the case" -There are many ways to select the content, depending on the user agent: mouse, hotkeys, taps on a mobile etc. +```smart header="Selection end/start vs Range" + +There's an important differences of a selection anchor/focus compared with a `Range` start/end. + +As we know, `Range` objects always have their start before the end. -Some of them, such as a mouse, allow the same selection can be created in two directions: "left-to-right" and "right-to-left". +For selections, that's not always the case. -If the start (anchor) of the selection goes in the document before the end (focus), this selection is said to have "forward" direction. +Selecting something with a mouse can be done in both directions: either "left-to-right" or "right-to-left". + +In other words, when the mouse button is pressed, and then it moves forward in the document, then its end (focus) will be after its start (anchor). E.g. if the user starts selecting with mouse and goes from "Example" to "italic": ![](selection-direction-forward.svg) -Otherwise, if they go from the end of "italic" to "Example", the selection is directed "backward", its focus will be before the anchor: +...But the same selection could be done backwards: starting from "italic" to "Example" (backward direction), then its end (focus) will be before the start (anchor): ![](selection-direction-backward.svg) - -That's different from `Range` objects that are always directed forward: the range start can't be after its end. -```` +``` ## Selection events There are events on to keep track of selection: -- `elem.onselectstart` -- when a selection starts on `elem`, e.g. the user starts moving mouse with pressed button. - - Preventing the default action makes the selection not start. -- `document.onselectionchange` -- whenever a selection changes. - - Please note: this handler can be set only on `document`. +- `elem.onselectstart` -- when a selection *starts* specifically on element `elem` (or inside it). For instance, when the user presses the mouse button on it and starts to move the pointer. + - Preventing the default action cancels the selection start. So starting a selection from this element becomes impossible, but the element is still selectable. The visitor just needs to start the selection from elsewhere. +- `document.onselectionchange` -- whenever a selection changes or starts. + - Please note: this handler can be set only on `document`, it tracks all selections in it. ### Selection tracking demo -Here's a small demo that shows selection boundaries dynamically as it changes: +Here's a small demo. It tracks the current selection on the `document` and shows its boundaries: ```html run height=80

    Select me: italic and bold

    @@ -331,21 +392,25 @@ Here's a small demo that shows selection boundaries dynamically as it changes: From – To ``` -### Selection getting demo +### Selection copying demo + +There are two approaches to copying the selected content: -To get the whole selection: -- As text: just call `document.getSelection().toString()`. -- As DOM nodes: get the underlying ranges and call their `cloneContents()` method (only first range if we don't support Firefox multiselection). +1. We can use `document.getSelection().toString()` to get it as text. +2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangesAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. -And here's the demo of getting the selection both as text and as DOM nodes: +Here's the demo of copying the selected content both as text and as DOM nodes: ```html run height=100

    Select me: italic and bold

    @@ -373,15 +438,15 @@ As text: ## Selection methods -Selection methods to add/remove ranges: +We can work with the selection by addding/removing ranges: -- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except firefox, only `0` is used. +- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. - `addRange(range)` -- add `range` to selection. All browsers except Firefox ignore the call, if the selection already has an associated range. - `removeRange(range)` -- remove `range` from the selection. - `removeAllRanges()` -- remove all ranges. - `empty()` -- alias to `removeAllRanges`. -Also, there are convenience methods to manipulate the selection range directly, without `Range`: +There are also convenience methods to manipulate the selection range directly, without intermediate `Range` calls: - `collapse(node, offset)` -- replace selected range with a new one that starts and ends at the given `node`, at position `offset`. - `setPosition(node, offset)` -- alias to `collapse`. @@ -393,7 +458,7 @@ Also, there are convenience methods to manipulate the selection range directly, - `deleteFromDocument()` -- remove selected content from the document. - `containsNode(node, allowPartialContainment = false)` -- checks whether the selection contains `node` (partially if the second argument is `true`) -So, for many tasks we can call `Selection` methods, no need to access the underlying `Range` object. +For most tasks these methods are just fine, there's no need to access the underlying `Range` object. For example, selecting the whole contents of the paragraph `

    `: @@ -420,10 +485,10 @@ The same thing using ranges: ``` -```smart header="To select, remove the existing selection first" -If the selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges. +```smart header="To select something, remove the existing selection first" +If a document selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges. -The exception is some selection methods, that replace the existing selection, like `setBaseAndExtent`. +The exception is some selection methods, that replace the existing selection, such as `setBaseAndExtent`. ``` ## Selection in form controls diff --git a/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg new file mode 100644 index 000000000..90054cef1 --- /dev/null +++ b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg @@ -0,0 +1 @@ +<p>Hello</p>p.firstChild \ No newline at end of file diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index a44308f6f..5031afa32 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -74,7 +74,7 @@ link.href = URL.createObjectURL(blob); We can also create a link dynamically in JavaScript and simulate a click by `link.click()`, then download starts automatically. -Here's the similar code that causes user to download the dynamicallly created `Blob`, without any HTML: +Here's the similar code that causes user to download the dynamically created `Blob`, without any HTML: ```js run let link = document.createElement('a'); @@ -186,7 +186,7 @@ let context = canvas.getContext('2d'); context.drawImage(img, 0, 0); // we can context.rotate(), and do many other things on canvas -// toBlob is async opereation, callback is called when done +// toBlob is async operation, callback is called when done canvas.toBlob(function(blob) { // blob ready, download it let link = document.createElement('a'); @@ -235,7 +235,7 @@ That makes Blobs convenient for upload/download operations, that are so common i Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types. -We can easily convert betweeen `Blob` and low-level binary data types: +We can easily convert between `Blob` and low-level binary data types: - We can make a Blob from a typed array using `new Blob(...)` constructor. - We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing. diff --git a/5-network/03-fetch-progress/article.md b/5-network/03-fetch-progress/article.md index f5a64da54..c0e3d922a 100644 --- a/5-network/03-fetch-progress/article.md +++ b/5-network/03-fetch-progress/article.md @@ -9,7 +9,7 @@ To track download progress, we can use `response.body` property. It's `ReadableS Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can count how much is consumed at any moment. -Here's the sketch of code that reads the reponse from `response.body`: +Here's the sketch of code that reads the response from `response.body`: ```js // instead of response.json() and other methods diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md index 14e8d13cb..af66c37cc 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -18,7 +18,7 @@ let controller = new AbortController(); A controller is an extremely simple object. - It has a single method `abort()`, -- And a single property `signal` that allows to set event liseners on it. +- And a single property `signal` that allows to set event listeners on it. When `abort()` is called: - `controller.signal` emits the `"abort"` event. diff --git a/5-network/06-fetch-api/article.md b/5-network/06-fetch-api/article.md index d187526fb..eea5030b6 100644 --- a/5-network/06-fetch-api/article.md +++ b/5-network/06-fetch-api/article.md @@ -171,7 +171,7 @@ The `redirect` option allows to change that: - **`"follow"`** -- the default, follow HTTP-redirects, - **`"error"`** -- error in case of HTTP-redirect, -- **`"manual"`** -- don't follow HTTP-redirect, but `response.url` will be the new URL, and `response.redirected` will be `true`, so that we can perform the redirect manually to the new URL (if needed). +- **`"manual"`** -- allows to process HTTP-redirects manually. In case of redirect, we'll get a special response object, with `response.type="opaqueredirect"` and zeroed/empty status and most other properies. ## integrity diff --git a/5-network/11-websocket/article.md b/5-network/11-websocket/article.md index aab6d31be..8193f8419 100644 --- a/5-network/11-websocket/article.md +++ b/5-network/11-websocket/article.md @@ -88,7 +88,7 @@ Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q== Sec-WebSocket-Version: 13 ``` -- `Origin` -- the origin of the client page, e.g. `https://javascript.info`. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compabitility issues. But `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. +- `Origin` -- the origin of the client page, e.g. `https://javascript.info`. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compatibility issues. But `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. - `Connection: Upgrade` -- signals that the client would like to change the protocol. - `Upgrade: websocket` -- the requested protocol is "websocket". - `Sec-WebSocket-Key` -- a random browser-generated key for security. diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index 9fc5f6955..ac2fda063 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -247,8 +247,11 @@ But anything more complicated, like a network request from another site or a for If that's fine for you, then adding `samesite=lax` will probably not break the user experience and add protection. -Overall, `samesite` is a great option, but it has an important drawback: -- `samesite` is ignored (not supported) by old browsers, year 2017 or so. +Overall, `samesite` is a great option. + +There's a drawback: + +- `samesite` is ignored (not supported) by very old browsers, year 2017 or so. **So if we solely rely on `samesite` to provide protection, then old browsers will be vulnerable.** diff --git a/6-data-storage/02-localstorage/article.md b/6-data-storage/02-localstorage/article.md index e2e2751db..d0483f3a3 100644 --- a/6-data-storage/02-localstorage/article.md +++ b/6-data-storage/02-localstorage/article.md @@ -127,17 +127,17 @@ Please note that both key and value must be strings. If were any other type, like a number, or an object, it gets converted to string automatically: ```js run -sessionStorage.user = {name: "John"}; -alert(sessionStorage.user); // [object Object] +localStorage.user = {name: "John"}; +alert(localStorage.user); // [object Object] ``` We can use `JSON` to store objects though: ```js run -sessionStorage.user = JSON.stringify({name: "John"}); +localStorage.user = JSON.stringify({name: "John"}); // sometime later -let user = JSON.parse( sessionStorage.user ); +let user = JSON.parse( localStorage.user ); alert( user.name ); // John ``` diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 7fd7168b1..19b5d7e9c 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -5,7 +5,7 @@ libs: # IndexedDB -IndexedDB is a database that is built into browser, much more powerful than `localStorage`. +IndexedDB is a database that is built into a browser, much more powerful than `localStorage`. - Stores almost any kind of values by keys, multiple key types. - Supports transactions for reliability. @@ -18,6 +18,12 @@ The native interface to IndexedDB, described in the specification . That's pretty convenient, but the wrapper is not perfect, it can't replace events for all cases. So we'll start with events, and then, after we gain an understanding of IndexedDb, we'll use the wrapper. +```smart header="Where's the data?" +Technically, the data is usually stored in the visitor's home directory, along with browser settings, extensions, etc. + +Different browsers and OS-level users have each their own independant storage. +``` + ## Open database To start working with IndexedDB, we first need to `open` (connect to) a database. @@ -44,7 +50,7 @@ Unlike server-side databases, IndexedDB is client-side, the data is stored in th If the local database version is less than specified in `open`, then a special event `upgradeneeded` is triggered, and we can compare versions and upgrade data structures as needed. -The `upgradeneeded` event also triggers when the database doesn't yet exist (technically, it's version is `0`), so we can perform the initialization. +The `upgradeneeded` event also triggers when the database doesn't yet exist (technically, its version is `0`), so we can perform the initialization. Let's say we published the first version of our app. @@ -89,7 +95,7 @@ openRequest.onupgradeneeded = function(event) { }; ``` -Please note: as our current version is `2`, `onupgradeneeded` handler has a code branch for version `0`, suitable for users that are accessing for the first time and have no database, and also for version `1`, for upgrades. +Please note: as our current version is `2`, the `onupgradeneeded` handler has a code branch for version `0`, suitable for users that are accessing for the first time and have no database, and also for version `1`, for upgrades. And then, only if `onupgradeneeded` handler finishes without errors, `openRequest.onsuccess` triggers, and the database is considered successfully opened. @@ -125,9 +131,7 @@ In order to organize that, the `versionchange` event triggers on the "outdated" If we don't listen for the `versionchange` event and don't close the old connection, then the second, new connection won't be made. The `openRequest` object will emit the `blocked` event instead of `success`. So the second tab won't work. -Here's the code to correctly handle the parallel upgrade. - -It installs an `onversionchange` handler after the database is opened, that closes the old connection: +Here's the code to correctly handle the parallel upgrade. It installs the `onversionchange` handler, that triggers if the current database connection becomes outdated (db version is updated elsewhere) and closes the connection. ```js let openRequest = indexedDB.open("store", 2); @@ -152,20 +156,22 @@ openRequest.onsuccess = function() { openRequest.onblocked = function() { // this event shouldn't trigger if we handle onversionchange correctly - // it means that there's another open connection to same database + // it means that there's another open connection to the same database // and it wasn't closed after db.onversionchange triggered for it }; */!* ``` -Here we do two things: +...In other words, here we do two things: + +1. The `db.onversionchange` listener informs us about a parallel update attempt, if the current database version becomes outdated. +2. The `openRequest.onblocked` listener informs us about the opposite situation: there's a connection to an outdated version elsewhere, and it doesn't close, so the newer connection can't be made. -1. Add `db.onversionchange` listener after a successful opening, to be informed about a parallel update attempt. -2. Add `openRequest.onblocked` listener to handle the case when an old connection wasn't closed. This doesn't happen if we close it in `db.onversionchange`. +We can handle things more gracefully in `db.onversionchange`, prompt the visitor to save the data before the connection is closed and so on. -There are other variants. For example, we can take the time to close things gracefully in `db.onversionchange`, and prompt the visitor to save the data before the connection is closed. The new updating connection will be blocked immediately after `db.onversionchange` has finished without closing, and we can ask the visitor in the new tab to close other tabs for the update. +Or, an alternative approach would be to not close the database in `db.onversionchange`, but instead use the `onblocked` handler (in the new tab) to alert the visitor, tell him that the newer version can't be loaded until they close other tabs. -These update collisions happen rarely, but we should at least have some handling for them, e.g. `onblocked` handler, so that our script doesn't surprise the user by dying silently. +These update collisions happen rarely, but we should at least have some handling for them, at least an `onblocked` handler, to prevent our script from dying silently. ## Object store @@ -183,7 +189,7 @@ An example of an object that can't be stored: an object with circular references **There must be a unique `key` for every value in the store.** -A key must be one of the these types - number, date, string, binary, or array. It's a unique identifier, so we can search/remove/update values by the key. +A key must be one of these types - number, date, string, binary, or array. It's a unique identifier, so we can search/remove/update values by the key. ![](indexeddb-structure.svg) @@ -247,7 +253,7 @@ db.deleteObjectStore('books') The term "transaction" is generic, used in many kinds of databases. -A transaction is a group operations, that should either all succeed or all fail. +A transaction is a group of operations, that should either all succeed or all fail. For instance, when a person buys something, we need to: 1. Subtract the money from their account. @@ -341,9 +347,9 @@ Usually, we can assume that a transaction commits when all its requests are comp So, in the example above no special call is needed to finish the transaction. -Transactions auto-commit principle has an important side effect. We can't insert an async operation like `fetch`, `setTimeout` in the middle of transaction. IndexedDB will not keep the transaction waiting till these are done. +Transactions auto-commit principle has an important side effect. We can't insert an async operation like `fetch`, `setTimeout` in the middle of a transaction. IndexedDB will not keep the transaction waiting till these are done. -In the code below, `request2` in line `(*)` fails, because the transaction is already committed, and can't make any request in it: +In the code below, `request2` in the line `(*)` fails, because the transaction is already committed, and can't make any request in it: ```js let request1 = books.add(book); @@ -364,7 +370,7 @@ That's because `fetch` is an asynchronous operation, a macrotask. Transactions a Authors of IndexedDB spec believe that transactions should be short-lived. Mostly for performance reasons. -Notably, `readwrite` transactions "lock" the stores for writing. So if one part of application initiated `readwrite` on `books` object store, then another part that wants to do the same has to wait: the new transaction "hangs" till the first one is done. That can lead to strange delays if transactions take a long time. +Notably, `readwrite` transactions "lock" the stores for writing. So if one part of the application initiated `readwrite` on `books` object store, then another part that wants to do the same has to wait: the new transaction "hangs" till the first one is done. That can lead to strange delays if transactions take a long time. So, what to do? @@ -469,24 +475,29 @@ request.onerror = function(event) { }; ``` -## Searching by keys +## Searching There are two main types of search in an object store: -1. By a key or a key range. That is: by `book.id` in our "books" storage. -2. By another object field, e.g. `book.price`. -First let's deal with the keys and key ranges `(1)`. +1. By a key value or a key range. In our "books" storage that would be a value or range of values of `book.id`. +2. By another object field, e.g. `book.price`. This required an additional data structure, named "index". -Methods that involve searching support either exact keys or so-called "range queries" -- [IDBKeyRange](https://www.w3.org/TR/IndexedDB/#keyrange) objects that specify a "key range". +### By key -Ranges are created using following calls: +First let's deal with the first type of search: by key. + +Searching methods support both exact key values and so-called "ranges of values" -- [IDBKeyRange](https://www.w3.org/TR/IndexedDB/#keyrange) objects that specify an acceptable "key range". + +`IDBKeyRange` objects are created using following calls: - `IDBKeyRange.lowerBound(lower, [open])` means: `≥lower` (or `>lower` if `open` is true) - `IDBKeyRange.upperBound(upper, [open])` means: `≤upper` (or ` 'js' +// get all keys, where id > 'js' books.getAllKeys(IDBKeyRange.lowerBound('js', true)) ``` ```smart header="Object store is always sorted" -Object store sorts values by key internally. +An object store sorts values by key internally. So requests that return many values always return them in sorted by key order. ``` - -## Searching by any field with an index +### By a field using an index To search by other object fields, we need to create an additional data structure named "index". @@ -782,7 +792,7 @@ await inventory.add({ id: 'js', price: 10, created: new Date() }); // Error The next `inventory.add` after `fetch` `(*)` fails with an "inactive transaction" error, because the transaction is already committed and closed at that time. -The workaround is same as when working with native IndexedDB: either make a new transaction or just split things apart. +The workaround is the same as when working with native IndexedDB: either make a new transaction or just split things apart. 1. Prepare the data and fetch all that's needed first. 2. Then save in the database. diff --git a/8-web-components/2-custom-elements/article.md b/8-web-components/2-custom-elements/article.md index 13af80b8c..a84ed1192 100644 --- a/8-web-components/2-custom-elements/article.md +++ b/8-web-components/2-custom-elements/article.md @@ -365,7 +365,7 @@ Our new button extends the built-in one. So it keeps the same styles and standar ## References - HTML Living Standard: . -- Compatiblity: . +- Compatiblity: . ## Summary @@ -397,4 +397,4 @@ Custom elements can be of two types: /*