(253-1)
(that's `9007199254740991`), or less than -(253-1)
for negatives. It's a technical limitation caused by their internal representation.
+
+For most purposes that's quite enough, but sometimes we need really big numbers, e.g. for cryptography or microsecond-precision timestamps.
+
+`BigInt` type was recently added to the language to represent integers of arbitrary length.
+
+A `BigInt` value is created by appending `n` to the end of an integer:
+
+```js
+// the "n" at the end means it's a BigInt
+const bigInt = 1234567890123456789012345678901234567890n;
+```
+
+As `BigInt` numbers are rarely needed, we don't cover them here, but devoted them a separate chapter `Hello`
.
-Double and single quotes are "simple" quotes. There's no difference between them in JavaScript.
+Double and single quotes are "simple" quotes. There's practically no difference between them in JavaScript.
Backticks are "extended functionality" quotes. They allow us to embed variables and expressions into a string by wrapping them in `${…}`, for example:
@@ -102,12 +128,12 @@ alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (double quotes do n
We'll cover strings more thoroughly in the chapter ±(253-1)
.
+- `bigint` is for integer numbers of arbitrary length.
+- `string` for strings. A string may have zero or more characters, there's no separate single-character type.
- `boolean` for `true`/`false`.
- `null` for unknown values -- a standalone type that has a single value `null`.
- `undefined` for unassigned values -- a standalone type that has a single value `undefined`.
diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md
similarity index 100%
rename from 1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md
rename to 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md
diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md
similarity index 100%
rename from 1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md
rename to 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md
diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md
similarity index 71%
rename from 1-js/02-first-steps/09-alert-prompt-confirm/article.md
rename to 1-js/02-first-steps/06-alert-prompt-confirm/article.md
index c14e0c85a..ef0f333cb 100644
--- a/1-js/02-first-steps/09-alert-prompt-confirm/article.md
+++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md
@@ -1,18 +1,10 @@
# Interaction: alert, prompt, confirm
-In this part of the tutorial we cover JavaScript language "as is", without environment-specific tweaks.
-
-But we'll still be using the browser as our demo environment, so we should know at least a few of its user-interface functions. In this chapter, we'll get familiar with the browser functions `alert`, `prompt` and `confirm`.
+As we'll be using the browser as our demo environment, let's see a couple of functions to interact with the user: `alert`, `prompt` and `confirm`.
## alert
-Syntax:
-
-```js
-alert(message);
-```
-
-This shows a message and pauses script execution until the user presses "OK".
+This one we've seen already. It shows a message and waits for the user to press "OK".
For example:
@@ -20,7 +12,7 @@ For example:
alert("Hello");
```
-The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc. until they have dealt with the window. In this case -- until they press "OK".
+The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc, until they have dealt with the window. In this case -- until they press "OK".
## prompt
@@ -30,7 +22,7 @@ The function `prompt` accepts two arguments:
result = prompt(title, [default]);
```
-It shows a modal window with a text message, an input field for the visitor, and the buttons OK/CANCEL.
+It shows a modal window with a text message, an input field for the visitor, and the buttons OK/Cancel.
`title`
: The text to show the visitor.
@@ -38,7 +30,11 @@ It shows a modal window with a text message, an input field for the visitor, and
`default`
: An optional second parameter, the initial value for the input field.
-The visitor may type something in the prompt input field and press OK. Or they can cancel the input by pressing CANCEL or hitting the `key:Esc` key.
+```smart header="The square brackets in syntax `[...]`"
+The square brackets around `default` in the syntax above denote that the parameter is optional, not required.
+```
+
+The visitor can type something in the prompt input field and press OK. Then we get that text in the `result`. Or they can cancel the input by pressing Cancel or hitting the `key:Esc` key, then we get `null` as the `result`.
The call to `prompt` returns the text from the input field or `null` if the input was canceled.
@@ -74,7 +70,7 @@ The syntax:
result = confirm(question);
```
-The function `confirm` shows a modal window with a `question` and two buttons: OK and CANCEL.
+The function `confirm` shows a modal window with a `question` and two buttons: OK and Cancel.
The result is `true` if OK is pressed and `false` otherwise.
@@ -94,10 +90,10 @@ We covered 3 browser-specific functions to interact with visitors:
: shows a message.
`prompt`
-: shows a message asking the user to input text. It returns the text or, if CANCEL or `key:Esc` is clicked, `null`.
+: shows a message asking the user to input text. It returns the text or, if Cancel button or `key:Esc` is clicked, `null`.
`confirm`
-: shows a message and waits for the user to press "OK" or "CANCEL". It returns `true` for OK and `false` for CANCEL/`key:Esc`.
+: shows a message and waits for the user to press "OK" or "Cancel". It returns `true` for OK and `false` for Cancel/`key:Esc`.
All these methods are modal: they pause script execution and don't allow the visitor to interact with the rest of the page until the window has been dismissed.
diff --git a/1-js/02-first-steps/06-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md
similarity index 78%
rename from 1-js/02-first-steps/06-type-conversions/article.md
rename to 1-js/02-first-steps/07-type-conversions/article.md
index 6ac695e84..cf97b3307 100644
--- a/1-js/02-first-steps/06-type-conversions/article.md
+++ b/1-js/02-first-steps/07-type-conversions/article.md
@@ -1,16 +1,18 @@
# Type Conversions
-Most of the time, operators and functions automatically convert the values given to them to the right type.
+Most of the time, operators and functions automatically convert the values given to them to the right type.
For example, `alert` automatically converts any value to a string to show it. Mathematical operations convert values to numbers.
There are also cases when we need to explicitly convert a value to the expected type.
```smart header="Not talking about objects yet"
-In this chapter, we won't cover objects. Instead, we'll study primitives first. Later, after we learn about objects, we'll see how object conversion works in the chapter true / false
| `1 / 0` |
| `string` | The string is read "as is", whitespaces from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. |
-**`ToBoolean`** -- Occurs in logical operations. Can be performed with `Boolean(value)`.
+**`Boolean Conversion`** -- Occurs in logical operations. Can be performed with `Boolean(value)`.
Follows the rules:
diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md
deleted file mode 100644
index 5c8bd2bc4..000000000
--- a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-```js no-beautify
-5 > 4 → true
-"apple" > "pineapple" → false
-"2" > "12" → true
-undefined == null → true
-undefined === null → false
-null == "\n0\n" → false
-null === +"\n0\n" → false
-```
-
-Some of the reasons:
-
-1. Obviously, true.
-2. Dictionary comparison, hence false.
-3. Again, dictionary comparison, first char of `"2"` is greater than the first char of `"1"`.
-4. Values `null` and `undefined` equal each other only.
-5. Strict equality is strict. Different types from both sides lead to false.
-6. See (4).
-7. Strict equality of different types.
diff --git a/1-js/02-first-steps/07-operators/1-increment-order/solution.md b/1-js/02-first-steps/08-operators/1-increment-order/solution.md
similarity index 100%
rename from 1-js/02-first-steps/07-operators/1-increment-order/solution.md
rename to 1-js/02-first-steps/08-operators/1-increment-order/solution.md
diff --git a/1-js/02-first-steps/07-operators/1-increment-order/task.md b/1-js/02-first-steps/08-operators/1-increment-order/task.md
similarity index 100%
rename from 1-js/02-first-steps/07-operators/1-increment-order/task.md
rename to 1-js/02-first-steps/08-operators/1-increment-order/task.md
diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/solution.md b/1-js/02-first-steps/08-operators/2-assignment-result/solution.md
similarity index 100%
rename from 1-js/02-first-steps/07-operators/2-assignment-result/solution.md
rename to 1-js/02-first-steps/08-operators/2-assignment-result/solution.md
diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/task.md b/1-js/02-first-steps/08-operators/2-assignment-result/task.md
similarity index 100%
rename from 1-js/02-first-steps/07-operators/2-assignment-result/task.md
rename to 1-js/02-first-steps/08-operators/2-assignment-result/task.md
diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md
similarity index 69%
rename from 1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md
rename to 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md
index 7dd0d61c2..dfd061cb6 100644
--- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md
+++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md
@@ -9,11 +9,11 @@ true + false = 1
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
-7 / 0 = Infinity
-" -9 " + 5 = " -9 5" // (3)
-" -9 " - 5 = -14 // (4)
+" -9 " + 5 = " -9 5" // (3)
+" -9 " - 5 = -14 // (4)
null + 1 = 1 // (5)
undefined + 1 = NaN // (6)
+" \t \n" - 2 = -2 // (7)
```
1. The addition with a string `"" + 1` converts `1` to a string: `"" + 1 = "1"`, and then we have `"1" + 0`, the same rule is applied.
@@ -22,3 +22,4 @@ undefined + 1 = NaN // (6)
4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it).
5. `null` becomes `0` after the numeric conversion.
6. `undefined` becomes `NaN` after the numeric conversion.
+7. Space characters, are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`.
diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md
similarity index 95%
rename from 1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md
rename to 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md
index f17e870de..068420c7d 100644
--- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md
+++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md
@@ -16,11 +16,11 @@ true + false
"$" + 4 + 5
"4" - 2
"4px" - 2
-7 / 0
" -9 " + 5
" -9 " - 5
null + 1
undefined + 1
+" \t \n" - 2
```
Think well, write down and then compare with the answer.
diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md
new file mode 100644
index 000000000..209a0702c
--- /dev/null
+++ b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md
@@ -0,0 +1,32 @@
+The reason is that prompt returns user input as a string.
+
+So variables have values `"1"` and `"2"` respectively.
+
+```js run
+let a = "1"; // prompt("First number?", 1);
+let b = "2"; // prompt("Second number?", 2);
+
+alert(a + b); // 12
+```
+
+What we should do is to convert strings to numbers before `+`. For example, using `Number()` or prepending them with `+`.
+
+For example, right before `prompt`:
+
+```js run
+let a = +prompt("First number?", 1);
+let b = +prompt("Second number?", 2);
+
+alert(a + b); // 3
+```
+
+Or in the `alert`:
+
+```js run
+let a = prompt("First number?", 1);
+let b = prompt("Second number?", 2);
+
+alert(+a + +b); // 3
+```
+
+Using both unary and binary `+` in the latest code. Looks funny, doesn't it?
diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/task.md b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md
new file mode 100644
index 000000000..b3ea4a3a3
--- /dev/null
+++ b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md
@@ -0,0 +1,18 @@
+importance: 5
+
+---
+
+# Fix the addition
+
+Here's a code that asks the user for two numbers and shows their sum.
+
+It works incorrectly. The output in the example below is `12` (for default prompt values).
+
+Why? Fix it. The result should be `3`.
+
+```js run
+let a = prompt("First number?", 1);
+let b = prompt("Second number?", 2);
+
+alert(a + b); // 12
+```
diff --git a/1-js/02-first-steps/07-operators/article.md b/1-js/02-first-steps/08-operators/article.md
similarity index 69%
rename from 1-js/02-first-steps/07-operators/article.md
rename to 1-js/02-first-steps/08-operators/article.md
index 74b27e871..decf5bcfc 100644
--- a/1-js/02-first-steps/07-operators/article.md
+++ b/1-js/02-first-steps/08-operators/article.md
@@ -1,8 +1,8 @@
-# Operators
+# Basic operators, maths
We know many operators from school. They are things like addition `+`, multiplication `*`, subtraction `-`, and so on.
-In this chapter, we'll concentrate on aspects of operators that are not covered by school arithmetic.
+In this chapter, we’ll start with simple operators, then concentrate on JavaScript-specific aspects, not covered by school arithmetic.
## Terms: "unary", "binary", "operand"
@@ -26,11 +26,61 @@ Before we move on, let's grasp some common terminology.
alert( y - x ); // 2, binary minus subtracts values
```
- Formally, we're talking about two different operators here: the unary negation (single operand: reverses the sign) and the binary subtraction (two operands: subtracts).
+ Formally, in the examples above we have two different operators that share the same symbol: the negation operator, a unary operator that reverses the sign, and the subtraction operator, a binary operator that subtracts one number from another.
-## String concatenation, binary +
+## Maths
-Now, let's see special features of JavaScript operators that are beyond school arithmetics.
+The following math operations are supported:
+
+- Addition `+`,
+- Subtraction `-`,
+- Multiplication `*`,
+- Division `/`,
+- Remainder `%`,
+- Exponentiation `**`.
+
+The first four are straightforward, while `%` and `**` need a few words about them.
+
+### Remainder %
+
+The remainder operator `%`, despite its appearance, is not related to percents.
+
+The result of `a % b` is the [remainder](https://en.wikipedia.org/wiki/Remainder) of the integer division of `a` by `b`.
+
+For instance:
+
+```js run
+alert( 5 % 2 ); // 1, a remainder of 5 divided by 2
+alert( 8 % 3 ); // 2, a remainder of 8 divided by 3
+```
+
+### Exponentiation **
+
+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 ); // 2² = 4
+alert( 2 ** 3 ); // 2³ = 8
+alert( 2 ** 4 ); // 2⁴ = 16
+```
+
+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)
+alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root)
+```
+
+
+## String concatenation with binary +
+
+Let's meet features of JavaScript operators that are beyond school arithmetics.
Usually, the plus operator `+` sums numbers.
@@ -41,7 +91,7 @@ let s = "my" + "string";
alert(s); // mystring
```
-Note that if one of the operands is a string, the other one is converted to a string too.
+Note that if any of the operands is a string, then the other one is converted to a string too.
For example:
@@ -50,22 +100,28 @@ alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
```
-See, it doesn't matter whether the first operand is a string or the second one. The rule is simple: if either operand is a string, the other one is converted into a string as well.
-
-However, note that operations run from left to right. If there are two numbers followed by a string, the numbers will be added before being converted to a string:
+See, it doesn't matter whether the first operand is a string or the second one.
+Here's a more complex example:
```js run
alert(2 + 2 + '1' ); // "41" and not "221"
```
-String concatenation and conversion is a special feature of the binary plus `+`. Other arithmetic operators work only with numbers and always convert their operands to numbers.
+Here, operators work one after another. The first `+` sums two numbers, so it returns `4`, then the next `+` adds the string `1` to it, so it's like `4 + '1' = '41'`.
+
+```js run
+alert('1' + 2 + 2); // "122" and not "14"
+```
+Here, the first operand is a string, the compiler treats the other two operands as strings too. The `2` gets concatenated to `'1'`, so it's like `'1' + 2 = "12"` and `"12" + 2 = "122"`.
-For instance, subtraction and division:
+The binary `+` is the only operator that supports strings in such a way. Other arithmetic operators work only with numbers and always convert their operands to numbers.
+
+Here's the demo for subtraction and division:
```js run
-alert( 2 - '1' ); // 1
-alert( '6' / '2' ); // 3
+alert( 6 - '2' ); // 4, converts '2' to a number
+alert( '6' / '2' ); // 3, converts both operands to numbers
```
## Numeric conversion, unary +
@@ -93,9 +149,7 @@ alert( +"" ); // 0
It actually does the same thing as `Number(...)`, but is shorter.
-The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings.
-
-What if we want to sum them?
+The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings. What if we want to sum them?
The binary plus would add them as strings:
@@ -127,30 +181,31 @@ Why are unary pluses applied to values before the binary ones? As we're going to
## Operator precedence
-If an expression has more than one operator, the execution order is defined by their *precedence*, or, in other words, the implicit priority order of operators.
+If an expression has more than one operator, the execution order is defined by their *precedence*, or, in other words, the default priority order of operators.
From school, we all know that the multiplication in the expression `1 + 2 * 2` should be calculated before the addition. That's exactly the precedence thing. The multiplication is said to have *a higher precedence* than the addition.
-Parentheses override any precedence, so if we're not satisfied with the implicit order, we can use them to change it. For example: `(1 + 2) * 2`.
+Parentheses override any precedence, so if we're not satisfied with the default order, we can use them to change it. For example, write `(1 + 2) * 2`.
There are many operators in JavaScript. Every operator has a corresponding precedence number. The one with the larger number executes first. If the precedence is the same, the execution order is from left to right.
-Here's an extract from the [precedence table](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones):
+Here's an extract from the [precedence table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones):
| Precedence | Name | Sign |
|------------|------|------|
| ... | ... | ... |
-| 16 | unary plus | `+` |
-| 16 | unary negation | `-` |
-| 14 | multiplication | `*` |
-| 14 | division | `/` |
+| 17 | unary plus | `+` |
+| 17 | unary negation | `-` |
+| 16 | exponentiation | `**` |
+| 15 | multiplication | `*` |
+| 15 | division | `/` |
| 13 | addition | `+` |
| 13 | subtraction | `-` |
| ... | ... | ... |
| 3 | assignment | `=` |
| ... | ... | ... |
-As we can see, the "unary plus" has a priority of `16` which is higher than the `13` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition.
+As we can see, the "unary plus" has a priority of `17` which is higher than the `13` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition.
## Assignment
@@ -164,24 +219,11 @@ let x = 2 * 2 + 1;
alert( x ); // 5
```
-It is possible to chain assignments:
-
-```js run
-let a, b, c;
-
-*!*
-a = b = c = 2 + 2;
-*/!*
-
-alert( a ); // 4
-alert( b ); // 4
-alert( c ); // 4
-```
+### Assignment = returns a value
-Chained assignments evaluate from right to left. First, the rightmost expression `2 + 2` is evaluated and then assigned to the variables on the left: `c`, `b` and `a`. At the end, all the variables share a single value.
+The fact of `=` being an operator, not a "magical" language construct has an interesting implication.
-````smart header="The assignment operator `\"=\"` returns a value"
-An operator always returns a value. That's obvious for most of them like addition `+` or multiplication `*`. But the assignment operator follows this rule too.
+All operators in JavaScript return a value. That's obvious for `+` and `-`, but also true for `=`.
The call `x = value` writes the `value` into `x` *and then returns it*.
@@ -199,51 +241,76 @@ alert( a ); // 3
alert( c ); // 0
```
-In the example above, the result of `(a = b + 1)` is the value which is assigned to `a` (that is `3`). It is then used to subtract from `3`.
+In the example above, the result of expression `(a = b + 1)` is the value which was assigned to `a` (that is `3`). It is then used for further evaluations.
-Funny code, isn't it? We should understand how it works, because sometimes we see it in 3rd-party libraries, but shouldn't write anything like that ourselves. Such tricks definitely don't make code clearer or readable.
-````
+Funny code, isn't it? We should understand how it works, because sometimes we see it in JavaScript libraries.
-## Remainder %
+Although, please don't write the code like that. Such tricks definitely don't make code clearer or readable.
-The remainder operator `%`, despite its appearance, is not related to percents.
-
-The result of `a % b` is the remainder of the integer division of `a` by `b`.
+### Chaining assignments
-For instance:
+Another interesting feature is the ability to chain assignments:
```js run
-alert( 5 % 2 ); // 1 is a remainder of 5 divided by 2
-alert( 8 % 3 ); // 2 is a remainder of 8 divided by 3
-alert( 6 % 3 ); // 0 is a remainder of 6 divided by 3
+let a, b, c;
+
+*!*
+a = b = c = 2 + 2;
+*/!*
+
+alert( a ); // 4
+alert( b ); // 4
+alert( c ); // 4
```
-## Exponentiation **
+Chained assignments evaluate from right to left. First, the rightmost expression `2 + 2` is evaluated and then assigned to the variables on the left: `c`, `b` and `a`. At the end, all the variables share a single value.
-The exponentiation operator `**` is a recent addition to the language.
+Once again, for the purposes of readability it's better to split such code into few lines:
-For a natural number `b`, the result of `a ** b` is `a` multiplied by itself `b` times.
+```js
+c = 2 + 2;
+b = c;
+a = c;
+```
+That's easier to read, especially when eye-scanning the code fast.
-For instance:
+## Modify-in-place
+
+We often need to apply an operator to a variable and store the new result in that same variable.
+
+For example:
+
+```js
+let n = 2;
+n = n + 5;
+n = n * 2;
+```
+
+This notation can be shortened using the operators `+=` and `*=`:
```js run
-alert( 2 ** 2 ); // 4 (2 * 2)
-alert( 2 ** 3 ); // 8 (2 * 2 * 2)
-alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2)
+let n = 2;
+n += 5; // now n = 7 (same as n = n + 5)
+n *= 2; // now n = 14 (same as n = n * 2)
+
+alert( n ); // 14
```
-The operator works for non-integer numbers as well.
+Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: `/=`, `-=`, etc.
-For instance:
+Such operators have the same precedence as a normal assignment, so they run after most other calculations:
```js run
-alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root, that's maths)
-alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root)
+let n = 2;
+
+n *= 3 + 5;
+
+alert( n ); // 16 (right part evaluated first, same as n *= 8)
```
## Increment/decrement
-
+
Increasing or decreasing a number by one is among the most common numerical operations.
@@ -253,14 +320,14 @@ So, there are special operators for it:
```js run no-beautify
let counter = 2;
- counter++; // works the same as counter = counter + 1, but is shorter
+ counter++; // works the same as counter = counter + 1, but is shorter
alert( counter ); // 3
```
- **Decrement** `--` decreases a variable by 1:
```js run no-beautify
let counter = 2;
- counter--; // works the same as counter = counter - 1, but is shorter
+ counter--; // works the same as counter = counter - 1, but is shorter
alert( counter ); // 1
```
@@ -370,41 +437,7 @@ The list of operators:
- RIGHT SHIFT ( `>>` )
- ZERO-FILL RIGHT SHIFT ( `>>>` )
-These operators are used very rarely. To understand them, we need to delve into low-level number representation and it would not be optimal to do that right now, especially since we won't need them any time soon. If you're curious, you can read the [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) article on MDN. It would be more practical to do that when a real need arises.
-
-## Modify-in-place
-
-We often need to apply an operator to a variable and store the new result in that same variable.
-
-For example:
-
-```js
-let n = 2;
-n = n + 5;
-n = n * 2;
-```
-
-This notation can be shortened using the operators `+=` and `*=`:
-
-```js run
-let n = 2;
-n += 5; // now n = 7 (same as n = n + 5)
-n *= 2; // now n = 14 (same as n = n * 2)
-
-alert( n ); // 14
-```
-
-Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: `/=`, `-=`, etc.
-
-Such operators have the same precedence as a normal assignment, so they run after most other calculations:
-
-```js run
-let n = 2;
-
-n *= 3 + 5;
-
-alert( n ); // 16 (right part evaluated first, same as n *= 8)
-```
+These operators are used very rarely, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise) chapter on MDN when a need arises.
## Comma
@@ -427,10 +460,10 @@ Here, the first expression `1 + 2` is evaluated and its result is thrown away. T
```smart header="Comma has a very low precedence"
Please note that the comma operator has very low precedence, lower than `=`, so parentheses are important in the example above.
-Without them: `a = 1 + 2, 3 + 4` evaluates `+` first, summing the numbers into `a = 3, 7`, then the assignment operator `=` assigns `a = 3`, and finally the number after the comma, `7`, is not processed so it's ignored.
+Without them: `a = 1 + 2, 3 + 4` evaluates `+` first, summing the numbers into `a = 3, 7`, then the assignment operator `=` assigns `a = 3`, and the rest is ignored. It's like `(a = 1 + 2), 3 + 4`.
```
-Why do we need an operator that throws away everything except the last part?
+Why do we need an operator that throws away everything except the last expression?
Sometimes, people use it in more complex constructs to put several actions in one line.
@@ -443,4 +476,4 @@ for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) {
}
```
-Such tricks are used in many JavaScript frameworks. That's why we're mentioning them. But, usually, they don't improve code readability so we should think well before using them.
+Such tricks are used in many JavaScript frameworks. That's why we're mentioning them. But usually they don't improve code readability so we should think well before using them.
diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md
new file mode 100644
index 000000000..632b1cf4e
--- /dev/null
+++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md
@@ -0,0 +1,21 @@
+
+
+```js no-beautify
+5 > 4 → true
+"apple" > "pineapple" → false
+"2" > "12" → true
+undefined == null → true
+undefined === null → false
+null == "\n0\n" → false
+null === +"\n0\n" → false
+```
+
+Some of the reasons:
+
+1. Obviously, true.
+2. Dictionary comparison, hence false. `"a"` is smaller than `"p"`.
+3. Again, dictionary comparison, first char `"2"` is greater than the first char `"1"`.
+4. Values `null` and `undefined` equal each other only.
+5. Strict equality is strict. Different types from both sides lead to false.
+6. Similar to `(4)`, `null` only equals `undefined`.
+7. Strict equality of different types.
diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md
similarity index 100%
rename from 1-js/02-first-steps/08-comparison/1-comparison-questions/task.md
rename to 1-js/02-first-steps/09-comparison/1-comparison-questions/task.md
diff --git a/1-js/02-first-steps/08-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md
similarity index 85%
rename from 1-js/02-first-steps/08-comparison/article.md
rename to 1-js/02-first-steps/09-comparison/article.md
index 8697076a4..ead7922ff 100644
--- a/1-js/02-first-steps/08-comparison/article.md
+++ b/1-js/02-first-steps/09-comparison/article.md
@@ -1,15 +1,21 @@
# Comparisons
-We know many comparison operators from maths:
+We know many comparison operators from maths.
+
+In JavaScript they are written like this:
- Greater/less than: a > b
, a < b
.
- Greater/less than or equals: a >= b
, a <= b
.
-- Equals: `a == b` (please note the double equals sign `=`. A single symbol `a = b` would mean an assignment).
-- Not equals. In maths the notation is ≠
, but in JavaScript it's written as an assignment with an exclamation sign before it: a != b
.
+- Equals: `a == b`, please note the double equality sign `==` means the equality test, while a single one `a = b` means an assignment.
+- Not equals. In maths the notation is ≠
, but in JavaScript it's written as a != b
.
+
+In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities.
+
+At the end you'll find a good recipe to avoid "JavaScript quirks"-related issues.
## Boolean is the result
-Like all other operators, a comparison returns a value. In this case, the value is a boolean.
+All comparison operators return a boolean value:
- `true` -- means "yes", "correct" or "the truth".
- `false` -- means "no", "wrong" or "not the truth".
@@ -51,7 +57,9 @@ The algorithm to compare two strings is simple:
4. Repeat until the end of either string.
5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater.
-In the examples above, the comparison `'Z' > 'A'` gets to a result at the first step while the strings `"Glow"` and `"Glee"` are compared character-by-character:
+In the first example above, the comparison `'Z' > 'A'` gets to a result at the first step.
+
+The second comparison `'Glow'` and `'Glee'` needs more steps as strings are compared character-by-character:
1. `G` is the same as `G`.
2. `l` is the same as `l`.
@@ -74,7 +82,7 @@ alert( '2' > 1 ); // true, string '2' becomes a number 2
alert( '01' == 1 ); // true, string '01' becomes a number 1
```
-For boolean values, `true` becomes `1` and `false` becomes `0`.
+For boolean values, `true` becomes `1` and `false` becomes `0`.
For example:
@@ -138,11 +146,8 @@ The strict equality operator is a bit longer to write, but makes it obvious what
## Comparison with null and undefined
-Let's see more edge cases.
-
There's a non-intuitive behavior when `null` or `undefined` are compared to other values.
-
For a strict equality check `===`
: These values are different, because each of them is a different type.
@@ -195,13 +200,12 @@ We get these results because:
- Comparisons `(1)` and `(2)` return `false` because `undefined` gets converted to `NaN` and `NaN` is a special numeric value which returns `false` for all comparisons.
- The equality check `(3)` returns `false` because `undefined` only equals `null`, `undefined`, and no other value.
-### Evade problems
-
-Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to evade problems with them:
+### Avoid problems
-Just treat any comparison with `undefined/null` except the strict equality `===` with exceptional care.
+Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to avoid problems with them:
-Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately.
+- Treat any comparison with `undefined/null` except the strict equality `===` with exceptional care.
+- Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately.
## Summary
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 1d1a3f33d..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,9 +6,8 @@ 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!"

[demo src="ifelse_task2"]
-
diff --git a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md
index 638ce81f1..ff32354fa 100644
--- a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md
+++ b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md
@@ -1,6 +1,6 @@
```js
-result = (a + b < 4) ? 'Below' : 'Over';
+let result = (a + b < 4) ? 'Below' : 'Over';
```
diff --git a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md
index 684e239f2..6bdf8453e 100644
--- a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md
+++ b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md
@@ -4,13 +4,14 @@ importance: 5
# Rewrite 'if' into '?'
-Rewrite this `if` using the ternary operator `'?'`:
+Rewrite this `if` using the conditional operator `'?'`:
```js
+let result;
+
if (a + b < 4) {
result = 'Below';
} else {
result = 'Over';
}
```
-
diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md
index 49c1fc041..51514062f 100644
--- a/1-js/02-first-steps/10-ifelse/article.md
+++ b/1-js/02-first-steps/10-ifelse/article.md
@@ -1,4 +1,4 @@
-# Conditional operators: if, '?'
+# Conditional branching: if, '?'
Sometimes, we need to perform different actions based on different conditions.
@@ -6,7 +6,7 @@ To do that, we can use the `if` statement and the conditional operator `?`, that
## The "if" statement
-The `if` statement evaluates a condition and, if the condition's result is `true`, executes a block of code.
+The `if(...)` statement evaluates a condition in parentheses and, if the result is `true`, executes a block of code.
For example:
@@ -68,7 +68,7 @@ if (cond) {
## The "else" clause
-The `if` statement may contain an optional "else" block. It executes when the condition is false.
+The `if` statement may contain an optional "else" block. It executes when the condition is falsy.
For example:
```js run
@@ -216,7 +216,7 @@ Depending on the condition `company == 'Netscape'`, either the first or the seco
We don't assign a result to a variable here. Instead, we execute different code depending on the condition.
-**We don't recommend using the question mark operator in this way.**
+**It's not recommended to use the question mark operator in this way.**
The notation is shorter than the equivalent `if` statement, which appeals to some programmers. But it is less readable.
diff --git a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md
index 8f4d664e8..f85b56366 100644
--- a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md
+++ b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md
@@ -6,7 +6,7 @@ alert( alert(1) || 2 || alert(3) );
The call to `alert` does not return a value. Or, in other words, it returns `undefined`.
-1. The first OR `||` evaluates it's left operand `alert(1)`. That shows the first message with `1`.
+1. The first OR `||` evaluates its left operand `alert(1)`. That shows the first message with `1`.
2. The `alert` returns `undefined`, so OR goes on to the second operand searching for a truthy value.
3. The second operand `2` is truthy, so the execution is halted, `2` is returned and then shown by the outer alert.
diff --git a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md
index cc00ca9fc..fc9e336c1 100644
--- a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md
+++ b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md
@@ -4,6 +4,6 @@ importance: 3
# Check the range between
-Write an "if" condition to check that `age` is between `14` and `90` inclusively.
+Write an `if` condition to check that `age` is between `14` and `90` inclusively.
"Inclusively" means that `age` can reach the edges `14` or `90`.
diff --git a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md
index 7c22d6ad1..9b947d00f 100644
--- a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md
+++ b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md
@@ -4,6 +4,6 @@ importance: 3
# Check the range outside
-Write an `if` condition to check that `age` is NOT between 14 and 90 inclusively.
+Write an `if` condition to check that `age` is NOT between `14` and `90` inclusively.
Create two variants: the first one using NOT `!`, the second one -- without it.
diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md
index b535650ec..604606259 100644
--- a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md
+++ b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md
@@ -3,19 +3,19 @@
```js run demo
let userName = prompt("Who's there?", '');
-if (userName == 'Admin') {
+if (userName === 'Admin') {
let pass = prompt('Password?', '');
- if (pass == 'TheMaster') {
+ if (pass === 'TheMaster') {
alert( 'Welcome!' );
- } else if (pass == '' || pass == null) {
- alert( 'Canceled.' );
+ } else if (pass === '' || pass === null) {
+ alert( 'Canceled' );
} else {
alert( 'Wrong password' );
}
-} else if (userName == '' || userName == null) {
+} else if (userName === '' || userName === null) {
alert( 'Canceled' );
} else {
alert( "I don't know you" );
diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/task.md b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md
index 0728efad1..290a52642 100644
--- a/1-js/02-first-steps/11-logical-operators/9-check-login/task.md
+++ b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md
@@ -6,13 +6,13 @@ importance: 3
Write the code which asks for a login with `prompt`.
-If the visitor enters `"Admin"`, then `prompt` for a password, if the input is an empty line or `key:Esc` -- show "Canceled.", if it's another string -- then show "I don't know you".
+If the visitor enters `"Admin"`, then `prompt` for a password, if the input is an empty line or `key:Esc` -- show "Canceled", if it's another string -- then show "I don't know you".
The password is checked as follows:
- If it equals "TheMaster", then show "Welcome!",
- Another string -- show "Wrong password",
-- For an empty string or cancelled input, show "Canceled."
+- For an empty string or cancelled input, show "Canceled"
The schema:
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 0773a10cb..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.
@@ -84,16 +84,16 @@ The OR `||` operator does the following:
A value is returned in its original form, without the conversion.
-In other words, a chain of OR `"||"` returns the first truthy value or the last one if no truthy value is found.
+In other words, a chain of OR `||` returns the first truthy value or the last one if no truthy value is found.
For instance:
```js run
alert( 1 || 0 ); // 1 (1 is truthy)
-alert( true || 'no matter what' ); // (true is truthy)
alert( null || 1 ); // 1 (1 is the first truthy value)
alert( null || 0 || 1 ); // 1 (the first truthy value)
+
alert( undefined || null || 0 ); // 0 (all falsy, returns the last value)
```
@@ -101,53 +101,40 @@ This leads to some interesting usage compared to a "pure, classical, boolean-onl
1. **Getting the first truthy value from a list of variables or expressions.**
- Imagine we have a list of variables which can either contain data or be `null/undefined`. How can we find the first one with data?
+ For instance, we have `firstName`, `lastName` and `nickName` variables, all optional (i.e. can be undefined or have falsy values).
- We can use OR `||`:
+ Let's use OR `||` to choose the one that has the data and show it (or `"Anonymous"` if nothing set):
```js run
- let currentUser = null;
- let defaultUser = "John";
+ let firstName = "";
+ let lastName = "";
+ let nickName = "SuperCoder";
*!*
- let name = currentUser || defaultUser || "unnamed";
+ alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder
*/!*
-
- alert( name ); // selects "John" – the first truthy value
```
- If both `currentUser` and `defaultUser` were falsy, `"unnamed"` would be the result.
-2. **Short-circuit evaluation.**
-
- Operands can be not only values, but arbitrary expressions. OR evaluates and tests them from left to right. The evaluation stops when a truthy value is reached, and the value is returned. This process is called "a short-circuit evaluation" because it goes as short as possible from left to right.
+ If all variables were falsy, `"Anonymous"` would show up.
- This is clearly seen when the expression given as the second argument has a side effect like a variable assignment.
+2. **Short-circuit evaluation.**
- In the example below, `x` does not get assigned:
+ Another feature of OR `||` operator is the so-called "short-circuit" evaluation.
- ```js run no-beautify
- let x;
+ It means that `||` processes its arguments until the first truthy value is reached, and then the value is returned immediately, without even touching the other argument.
- *!*true*/!* || (x = 1);
+ That importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call.
- alert(x); // undefined, because (x = 1) not evaluated
- ```
-
- If, instead, the first argument is `false`, `||` evaluates the second one, thus running the assignment:
+ In the example below, only the second message is printed:
```js run no-beautify
- let x;
-
- *!*false*/!* || (x = 1);
-
- alert(x); // 1
+ *!*true*/!* || alert("not printed");
+ *!*false*/!* || alert("printed");
```
- An assignment is a simple case. There may be side effects, that won't show up if the evaluation doesn't reach them.
+ In the first line, the OR `||` operator stops the evaluation immediately upon seeing `true`, so the `alert` isn't run.
- As we can see, such a use case is a "shorter way of doing `if`". The first operand is converted to boolean. If it's false, the second one is evaluated.
-
- Most of time, it's better to use a "regular" `if` to keep the code easy to understand, but sometimes this can be handy.
+ Sometimes, people use this feature to execute commands only if the condition on the left part is falsy.
## && (AND)
@@ -186,7 +173,7 @@ if (1 && 0) { // evaluated as true && false
```
-## AND finds the first falsy value
+## AND "&&" finds the first falsy value
Given multiple AND'ed values:
@@ -236,7 +223,8 @@ The precedence of AND `&&` operator is higher than OR `||`.
So the code `a && b || c && d` is essentially the same as if the `&&` expressions were in parentheses: `(a && b) || (c && d)`.
````
-Just like OR, the AND `&&` operator can sometimes replace `if`.
+````warn header="Don't replace `if` with `||` or `&&`"
+Sometimes, people use the AND `&&` operator as a "shorter way to write `if`".
For instance:
@@ -253,14 +241,12 @@ So we basically have an analogue for:
```js run
let x = 1;
-if (x > 0) {
- alert( 'Greater than zero!' );
-}
+if (x > 0) alert( 'Greater than zero!' );
```
-The variant with `&&` appears shorter. But `if` is more obvious and tends to be a little bit more readable.
+Although, the variant with `&&` appears shorter, `if` is more obvious and tends to be a little bit more readable. So we recommend using every construct for its purpose: use `if` if we want `if` and use `&&` if we want AND.
+````
-So we recommend using every construct for its purpose: use `if` if we want if and use `&&` if we want AND.
## ! (NOT)
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
new file mode 100644
index 000000000..b84dff892
--- /dev/null
+++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md
@@ -0,0 +1,169 @@
+# Nullish coalescing operator '??'
+
+[recent browser="new"]
+
+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.
+
+We can rewrite `result = a ?? b` using the operators that we already know, like this:
+
+```js
+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 `user` if defined, otherwise `Anonymous`:
+
+```js run
+let user;
+
+alert(user ?? "Anonymous"); // Anonymous (user not defined)
+```
+
+Here's the example with `user` assigned to a name:
+
+```js run
+let user = "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 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 aren't defined.
+
+Let's use the `??` operator for that:
+
+```js run
+let firstName = null;
+let lastName = null;
+let nickName = "Supercoder";
+
+// shows the first defined value:
+*!*
+alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder
+*/!*
+```
+
+## Comparison with ||
+
+The OR `||` operator can be used in the same way as `??`, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value).
+
+For example, in the code above we could replace `??` with `||` and still get the same result:
+
+```js run
+let firstName = null;
+let lastName = null;
+let nickName = "Supercoder";
+
+// shows the first truthy value:
+*!*
+alert(firstName || lastName || nickName || "Anonymous"); // Supercoder
+*/!*
+```
+
+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 `||`.
+
+The important difference between them is that:
+- `||` returns the first *truthy* value.
+- `??` returns the first *defined* value.
+
+In other words, `||` doesn't distinguish between `false`, `0`, an empty string `""` and `null/undefined`. They are all the same -- falsy values. If any of these is the first argument of `||`, then we'll get the second argument as the result.
+
+In practice though, we may want to use default value only when the variable is `null/undefined`. That is, when the value is really unknown/not set.
+
+For example, consider this:
+
+```js run
+let height = 0;
+
+alert(height || 100); // 100
+alert(height ?? 100); // 0
+```
+
+- 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`.
+
+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 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:
+
+```js run
+let height = null;
+let width = null;
+
+// important: use parentheses
+let area = (height ?? 100) * (width ?? 50);
+
+alert(area); // 5000
+```
+
+Otherwise, if we omit parentheses, then as `*` has the higher precedence than `??`, it would execute first, leading to incorrect results.
+
+```js
+// without parentheses
+let area = height ?? 100 * width ?? 50;
+
+// ...works the same as this (probably not what we want):
+let area = height ?? (100 * width) ?? 50;
+```
+
+### Using ?? with && or ||
+
+Due to safety reasons, JavaScript forbids using `??` together with `&&` and `||` operators, unless the precedence is explicitly specified with parentheses.
+
+The code below triggers a syntax error:
+
+```js run
+let x = 1 && 2 ?? 3; // Syntax error
+```
+
+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:
+
+```js run
+*!*
+let x = (1 && 2) ?? 3; // Works
+*/!*
+
+alert(x); // 2
+```
+
+## Summary
+
+- The nullish coalescing operator `??` provides a short way to choose the first "defined" value from a list.
+
+ It's used to assign default values to variables:
+
+ ```js
+ // set height=100, if height is null or undefined
+ height = height ?? 100;
+ ```
+
+- The operator `??` has a very low precedence, only a bit higher than `?` and `=`, so consider adding parentheses when using it in an expression.
+- It's forbidden to use it with `||` or `&&` without explicit parentheses.
diff --git a/1-js/02-first-steps/12-while-for/1-loop-last-value/solution.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/1-loop-last-value/solution.md
rename to 1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md
diff --git a/1-js/02-first-steps/12-while-for/1-loop-last-value/task.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/1-loop-last-value/task.md
rename to 1-js/02-first-steps/13-while-for/1-loop-last-value/task.md
diff --git a/1-js/02-first-steps/12-while-for/2-which-value-while/solution.md b/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/2-which-value-while/solution.md
rename to 1-js/02-first-steps/13-while-for/2-which-value-while/solution.md
diff --git a/1-js/02-first-steps/12-while-for/2-which-value-while/task.md b/1-js/02-first-steps/13-while-for/2-which-value-while/task.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/2-which-value-while/task.md
rename to 1-js/02-first-steps/13-while-for/2-which-value-while/task.md
diff --git a/1-js/02-first-steps/12-while-for/3-which-value-for/solution.md b/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/3-which-value-for/solution.md
rename to 1-js/02-first-steps/13-while-for/3-which-value-for/solution.md
diff --git a/1-js/02-first-steps/12-while-for/3-which-value-for/task.md b/1-js/02-first-steps/13-while-for/3-which-value-for/task.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/3-which-value-for/task.md
rename to 1-js/02-first-steps/13-while-for/3-which-value-for/task.md
diff --git a/1-js/02-first-steps/12-while-for/4-for-even/solution.md b/1-js/02-first-steps/13-while-for/4-for-even/solution.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/4-for-even/solution.md
rename to 1-js/02-first-steps/13-while-for/4-for-even/solution.md
diff --git a/1-js/02-first-steps/12-while-for/4-for-even/task.md b/1-js/02-first-steps/13-while-for/4-for-even/task.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/4-for-even/task.md
rename to 1-js/02-first-steps/13-while-for/4-for-even/task.md
diff --git a/1-js/02-first-steps/12-while-for/5-replace-for-while/solution.md b/1-js/02-first-steps/13-while-for/5-replace-for-while/solution.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/5-replace-for-while/solution.md
rename to 1-js/02-first-steps/13-while-for/5-replace-for-while/solution.md
diff --git a/1-js/02-first-steps/12-while-for/5-replace-for-while/task.md b/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/5-replace-for-while/task.md
rename to 1-js/02-first-steps/13-while-for/5-replace-for-while/task.md
diff --git a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md
similarity index 80%
rename from 1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md
rename to 1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md
index 2e04a78c4..c7de5f09b 100644
--- a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md
+++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md
@@ -10,6 +10,6 @@ do {
The loop `do..while` repeats while both checks are truthy:
1. The check for `num <= 100` -- that is, the entered value is still not greater than `100`.
-2. The check `&& num` is false when `num` is `null` or a empty string. Then the `while` loop stops too.
+2. The check `&& num` is false when `num` is `null` or an empty string. Then the `while` loop stops too.
P.S. If `num` is `null` then `num <= 100` is `true`, so without the 2nd check the loop wouldn't stop if the user clicks CANCEL. Both checks are required.
diff --git a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/task.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/6-repeat-until-correct/task.md
rename to 1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md
diff --git a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md
similarity index 53%
rename from 1-js/02-first-steps/12-while-for/7-list-primes/solution.md
rename to 1-js/02-first-steps/13-while-for/7-list-primes/solution.md
index 9ff0663d7..b4b64b6fa 100644
--- a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md
+++ b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md
@@ -26,4 +26,4 @@ for (let i = 2; i <= n; i++) { // for each i...
}
```
-There's a lot of space to opimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc.
+There's a lot of space to optimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc.
diff --git a/1-js/02-first-steps/12-while-for/7-list-primes/task.md b/1-js/02-first-steps/13-while-for/7-list-primes/task.md
similarity index 100%
rename from 1-js/02-first-steps/12-while-for/7-list-primes/task.md
rename to 1-js/02-first-steps/13-while-for/7-list-primes/task.md
diff --git a/1-js/02-first-steps/12-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md
similarity index 88%
rename from 1-js/02-first-steps/12-while-for/article.md
rename to 1-js/02-first-steps/13-while-for/article.md
index c809581f5..a7a211569 100644
--- a/1-js/02-first-steps/12-while-for/article.md
+++ b/1-js/02-first-steps/13-while-for/article.md
@@ -17,7 +17,7 @@ while (condition) {
}
```
-While the `condition` is `true`, the `code` from the loop body is executed.
+While the `condition` is truthy, the `code` from the loop body is executed.
For instance, the loop below outputs `i` while `i < 3`:
@@ -84,7 +84,7 @@ This form of syntax should only be used when you want the body of the loop to ex
## The "for" loop
-The `for` loop is the most commonly used loop.
+The `for` loop is more complex, but it's also the most commonly used loop.
It looks like this:
@@ -106,13 +106,13 @@ Let's examine the `for` statement part-by-part:
| part | | |
|-------|----------|----------------------------------------------------------------------------|
-| begin | `i = 0` | Executes once upon entering the loop. |
+| begin | `let i = 0` | Executes once upon entering the loop. |
| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. |
-| step| `i++` | Executes after the body on each iteration but before the condition check. |
| body | `alert(i)`| Runs again and again while the condition is truthy. |
-
+| step| `i++` | Executes after the body on each iteration. |
The general loop algorithm works like this:
+
```
Run begin
→ (if condition → run body and run step)
@@ -121,6 +121,8 @@ Run begin
→ ...
```
+That is, `begin` executes once, and then it iterates: after each `condition` test, `body` and `step` are executed.
+
If you are new to loops, it could help to go back to the example and reproduce how it runs step-by-step on a piece of paper.
Here's exactly what happens in our case:
@@ -210,7 +212,7 @@ But we can force the exit at any time using the special `break` directive.
For example, the loop below asks the user for a series of numbers, "breaking" when no number is entered:
-```js
+```js run
let sum = 0;
while (true) {
@@ -254,7 +256,7 @@ For even values of `i`, the `continue` directive stops executing the body and pa
````smart header="The `continue` directive helps decrease nesting"
A loop that shows odd values could look like this:
-```js
+```js run
for (let i = 0; i < 10; i++) {
if (i % 2) {
@@ -266,7 +268,7 @@ for (let i = 0; i < 10; i++) {
From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`.
-But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of`if` is longer than a few lines, that may decrease the overall readability.
+But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability.
````
````warn header="No `break/continue` to the right side of '?'"
@@ -289,8 +291,7 @@ if (i > 5) {
(i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here
```
-...it stops working. Code like this will give a syntax error:
-
+...it stops working: there's a syntax error.
This is just another reason not to use the question mark operator `?` instead of `if`.
````
@@ -299,7 +300,7 @@ This is just another reason not to use the question mark operator `?` instead of
Sometimes we need to break out from multiple nested loops at once.
-For example, in the code below we loop over `i` and `j`, prompting for the coordinates `(i, j)` from `(0,0)` to `(3,3)`:
+For example, in the code below we loop over `i` and `j`, prompting for the coordinates `(i, j)` from `(0,0)` to `(2,2)`:
```js run no-beautify
for (let i = 0; i < 3; i++) {
@@ -308,8 +309,7 @@ for (let i = 0; i < 3; i++) {
let input = prompt(`Value at coords (${i},${j})`, '');
- // what if I want to exit from here to Done (below)?
-
+ // what if we want to exit from here to Done (below)?
}
}
@@ -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
@@ -358,17 +358,28 @@ for (let i = 0; i < 3; i++) { ... }
The `continue` directive can also be used with a label. In this case, code execution jumps to the next iteration of the labeled loop.
-````warn header="Labels are not a \"goto\""
+````warn header="Labels do not allow to \"jump\" anywhere"
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; // jumps to label? No.
+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` is used 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/14-function-basics/function_basics.png b/1-js/02-first-steps/14-function-basics/function_basics.png
deleted file mode 100644
index f5e6f9418..000000000
Binary files a/1-js/02-first-steps/14-function-basics/function_basics.png and /dev/null differ
diff --git a/1-js/02-first-steps/14-function-basics/function_basics@2x.png b/1-js/02-first-steps/14-function-basics/function_basics@2x.png
deleted file mode 100644
index c31b2636a..000000000
Binary files a/1-js/02-first-steps/14-function-basics/function_basics@2x.png and /dev/null differ
diff --git a/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/solution.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md
similarity index 100%
rename from 1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/solution.md
rename to 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md
diff --git a/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/task.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md
similarity index 100%
rename from 1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/task.md
rename to 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md
diff --git a/1-js/02-first-steps/13-switch/2-rewrite-if-switch/solution.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md
similarity index 100%
rename from 1-js/02-first-steps/13-switch/2-rewrite-if-switch/solution.md
rename to 1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md
diff --git a/1-js/02-first-steps/13-switch/2-rewrite-if-switch/task.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md
similarity index 100%
rename from 1-js/02-first-steps/13-switch/2-rewrite-if-switch/task.md
rename to 1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md
diff --git a/1-js/02-first-steps/13-switch/article.md b/1-js/02-first-steps/14-switch/article.md
similarity index 97%
rename from 1-js/02-first-steps/13-switch/article.md
rename to 1-js/02-first-steps/14-switch/article.md
index 258f24068..effdafcf9 100644
--- a/1-js/02-first-steps/13-switch/article.md
+++ b/1-js/02-first-steps/14-switch/article.md
@@ -47,7 +47,7 @@ switch (a) {
break;
*/!*
case 5:
- alert( 'Too large' );
+ alert( 'Too big' );
break;
default:
alert( "I don't know such values" );
@@ -117,7 +117,7 @@ Several variants of `case` which share the same code can be grouped.
For example, if we want the same code to run for `case 3` and `case 5`:
```js run no-beautify
-let a = 2 + 2;
+let a = 3;
switch (a) {
case 4:
@@ -125,7 +125,7 @@ switch (a) {
break;
*!*
- case 3: // (*) grouped two cases
+ case 3: // (*) grouped two cases
case 5:
alert('Wrong!');
alert("Why don't you take a math class?");
diff --git a/1-js/02-first-steps/14-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md
similarity index 100%
rename from 1-js/02-first-steps/14-function-basics/1-if-else-required/solution.md
rename to 1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md
diff --git a/1-js/02-first-steps/14-function-basics/1-if-else-required/task.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md
similarity index 100%
rename from 1-js/02-first-steps/14-function-basics/1-if-else-required/task.md
rename to 1-js/02-first-steps/15-function-basics/1-if-else-required/task.md
diff --git a/1-js/02-first-steps/14-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
similarity index 89%
rename from 1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/solution.md
rename to 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/14-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/14-function-basics/2-rewrite-function-question-or/task.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md
similarity index 85%
rename from 1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md
rename to 1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md
index 523bb127a..46da079c0 100644
--- a/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md
+++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md
@@ -13,7 +13,7 @@ function checkAge(age) {
if (age > 18) {
return true;
} else {
- return confirm('Do you have your parents permission to access this page?');
+ return confirm('Did parents allow you?');
}
}
```
diff --git a/1-js/02-first-steps/14-function-basics/3-min/solution.md b/1-js/02-first-steps/15-function-basics/3-min/solution.md
similarity index 100%
rename from 1-js/02-first-steps/14-function-basics/3-min/solution.md
rename to 1-js/02-first-steps/15-function-basics/3-min/solution.md
diff --git a/1-js/02-first-steps/14-function-basics/3-min/task.md b/1-js/02-first-steps/15-function-basics/3-min/task.md
similarity index 100%
rename from 1-js/02-first-steps/14-function-basics/3-min/task.md
rename to 1-js/02-first-steps/15-function-basics/3-min/task.md
diff --git a/1-js/02-first-steps/14-function-basics/4-pow/solution.md b/1-js/02-first-steps/15-function-basics/4-pow/solution.md
similarity index 75%
rename from 1-js/02-first-steps/14-function-basics/4-pow/solution.md
rename to 1-js/02-first-steps/15-function-basics/4-pow/solution.md
index 5ef20c386..19fe9011f 100644
--- a/1-js/02-first-steps/14-function-basics/4-pow/solution.md
+++ b/1-js/02-first-steps/15-function-basics/4-pow/solution.md
@@ -14,10 +14,8 @@ let x = prompt("x?", '');
let n = prompt("n?", '');
if (n < 1) {
- alert(`Power ${n} is not supported,
- use an integer greater than 0`);
+ alert(`Power ${n} is not supported, use a positive integer`);
} else {
alert( pow(x, n) );
}
```
-
diff --git a/1-js/02-first-steps/14-function-basics/4-pow/task.md b/1-js/02-first-steps/15-function-basics/4-pow/task.md
similarity index 100%
rename from 1-js/02-first-steps/14-function-basics/4-pow/task.md
rename to 1-js/02-first-steps/15-function-basics/4-pow/task.md
diff --git a/1-js/02-first-steps/14-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md
similarity index 79%
rename from 1-js/02-first-steps/14-function-basics/article.md
rename to 1-js/02-first-steps/15-function-basics/article.md
index ec34b744d..b46f42924 100644
--- a/1-js/02-first-steps/14-function-basics/article.md
+++ b/1-js/02-first-steps/15-function-basics/article.md
@@ -20,9 +20,13 @@ function showMessage() {
}
```
-The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (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(parameter1, parameter2, ... parameterN) {
+ ...body...
+}
+```
Our new function can be called by its name: `showMessage()`.
@@ -133,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) {
@@ -171,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 `showMessage` 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:
@@ -181,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"*/!*) {
@@ -205,42 +218,57 @@ 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 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. This is in contrast to some other languages like Python, where any default parameters are evaluated only once during the initial interpretation.
+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
-````smart header="Default parameters old-style"
-Old editions of JavaScript did not support default parameters. So there are alternative ways to support them, that you can find mostly in the old scripts.
+Sometimes it makes sense to assign default values for parameters not in the function declaration, but at a later stage.
-For instance, an explicit check for being `undefined`:
+We can check if the parameter is passed during the function execution, by comparing it with `undefined`:
+
+```js run
+function showMessage(text) {
+ // ...
-```js
-function showMessage(from, text) {
*!*
- if (text === undefined) {
- text = 'no text given';
+ if (text === undefined) { // if the parameter is missing
+ text = 'empty message';
}
*/!*
- alert( from + ": " + text );
+ alert(text);
}
+
+showMessage(); // empty message
```
-...Or the `||` operator:
+...Or we could use the `||` operator:
```js
-function showMessage(from, text) {
- // if text is falsy then text gets the "default" value
- text = text || 'no text given';
+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 most falsy values, such as `0`, should be considered "normal":
-````
+```js run
+function showCount(count) {
+ // if count is undefined or null, show "unknown"
+ alert(count ?? "unknown");
+}
+showCount(0); // 0
+showCount(null); // unknown
+showCount(); // unknown
+```
## Returning a value
@@ -263,7 +291,7 @@ There may be many occurrences of `return` in a single function. For instance:
```js run
function checkAge(age) {
- if (age > 18) {
+ if (age >= 18) {
*!*
return true;
*/!*
@@ -335,7 +363,19 @@ That doesn't work, because JavaScript assumes a semicolon after `return`. That'l
return*!*;*/!*
(some + long + expression + or + whatever * f(a) + f(b))
```
-So, it effectively becomes an empty return. We should put the value on the same line instead.
+
+So, it effectively becomes an empty return.
+
+If we want the returned expression to wrap across multiple lines, we should start it at the same line as `return`. Or at least put the opening parentheses there as follows:
+
+```js
+return (
+ some + long + expression
+ + or +
+ whatever * f(a) + f(b)
+ )
+```
+And it will work just as we expect it to.
````
## Naming a function [#function-naming]
@@ -384,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/15-function-expressions-arrows/article.md b/1-js/02-first-steps/16-function-expressions/article.md
similarity index 61%
rename from 1-js/02-first-steps/15-function-expressions-arrows/article.md
rename to 1-js/02-first-steps/16-function-expressions/article.md
index 9b63907d5..a8ccd6c6c 100644
--- a/1-js/02-first-steps/15-function-expressions-arrows/article.md
+++ b/1-js/02-first-steps/16-function-expressions/article.md
@@ -1,4 +1,4 @@
-# Function expressions and arrows
+# Function expressions
In JavaScript, a function is not a "magical language structure", but a special kind of value.
@@ -22,7 +22,6 @@ let sayHi = function() {
Here, the function is created and assigned to the variable explicitly, like any other value. No matter how the function is defined, it's just a value stored in the variable `sayHi`.
-
The meaning of these code samples is the same: "create a function and put it into the variable `sayHi`".
We can even print out that value using `alert`:
@@ -41,7 +40,7 @@ Please note that the last line does not run the function, because there are no p
In JavaScript, a function is a value, so we can deal with it as a value. The code above shows its string representation, which is the source code.
-It is a special value of course, in the sense that we can call it like `sayHi()`.
+Surely, a function is a special value, in the sense that we can call it like `sayHi()`.
But it's still a value. So we can work with it like with other kinds of values.
@@ -61,21 +60,21 @@ sayHi(); // Hello // this still works too (why wouldn't it)
Here's what happens above in detail:
1. The Function Declaration `(1)` creates the function and puts it into the variable named `sayHi`.
-2. Line `(2)` copies it into the variable `func`.
-
- Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself.
+2. Line `(2)` copies it into the variable `func`. Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself.
3. Now the function can be called as both `sayHi()` and `func()`.
Note that we could also have used a Function Expression to declare `sayHi`, in the first line:
```js
-let sayHi = function() { ... };
+let sayHi = function() {
+ alert( "Hello" );
+};
let func = sayHi;
// ...
```
-Everything would work the same. Even more obvious what's going on, right?
+Everything would work the same.
````smart header="Why is there a semicolon at the end?"
@@ -93,7 +92,7 @@ let sayHi = function() {
The answer is simple:
- There's no need for `;` at the end of code blocks and syntax structures that use them like `if { ... }`, `for { }`, `function f { }` etc.
-- A Function Expression is used inside the statement: `let sayHi = ...;`, as a value. It's not a code block. The semicolon `;` is recommended at the end of statements, no matter what is the value. So the semicolon here is not related to the Function Expression itself in any way, it just terminates the statement.
+- A Function Expression is used inside the statement: `let sayHi = ...;`, as a value. It's not a code block, but rather an assignment. The semicolon `;` is recommended at the end of statements, no matter what the value is. So the semicolon here is not related to the Function Expression itself, it just terminates the statement.
````
## Callback functions
@@ -133,11 +132,11 @@ function showCancel() {
ask("Do you agree?", showOk, showCancel);
```
-Before we explore how we can write it in a much shorter way, let's note that in the browser (and on the server-side in some cases) such functions are quite popular. The major difference between a real-life implementation and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such a function usually draws a nice-looking question window. But that's another story.
+In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such function usually draws a nice-looking question window. But that's another story.
-**The arguments of `ask` are called *callback functions* or just *callbacks*.**
+**The arguments `showOk` and `showCancel` of `ask` are called *callback functions* or just *callbacks*.**
-The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for the "yes" answer, and `showCancel` for the "no" answer.
+The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for "yes" answer, and `showCancel` for "no" answer.
We can use Function Expressions to write the same function much shorter:
@@ -156,12 +155,10 @@ ask(
*/!*
```
-
Here, functions are declared right inside the `ask(...)` call. They have no name, and so are called *anonymous*. Such functions are not accessible outside of `ask` (because they are not assigned to variables), but that's just what we want here.
Such code appears in our scripts very naturally, it's in the spirit of JavaScript.
-
```smart header="A function is a value representing an \"action\""
Regular values like strings or numbers represent the *data*.
@@ -196,19 +193,19 @@ First, the syntax: how to differentiate between them in the code.
The more subtle difference is *when* a function is created by the JavaScript engine.
-**A Function Expression is created when the execution reaches it and is usable from then on.**
+**A Function Expression is created when the execution reaches it and is usable only from that moment.**
Once the execution flow passes to the right side of the assignment `let sum = function…` -- here we go, the function is created and can be used (assigned, called, etc. ) from now on.
Function Declarations are different.
-**A Function Declaration is usable in the whole script (or a code block, if it's inside a block).**
+**A Function Declaration can be called earlier than it is defined.**
-In other words, when JavaScript *prepares* to run the script or a code block, it first looks for Function Declarations in it and creates the functions. We can think of it as an "initialization stage".
+For example, a global Function Declaration is visible in the whole script, no matter where it is.
-And after all of the Function Declarations are processed, the execution goes on.
+That's due to internal algorithms. When JavaScript prepares to run the script, it first looks for global Function Declarations in it and creates the functions. We can think of it as an "initialization stage".
-As a result, a function declared as a Function Declaration can be called earlier than it is defined.
+And after all Function Declarations are processed, the code is executed. So it has access to these functions.
For example, this works:
@@ -224,7 +221,7 @@ function sayHi(name) {
The Function Declaration `sayHi` is created when JavaScript is preparing to start the script and is visible everywhere in it.
-...If it was a Function Expression, then it wouldn't work:
+...If it were a Function Expression, then it wouldn't work:
```js run refresh untrusted
*!*
@@ -238,13 +235,13 @@ let sayHi = function(name) { // (*) no magic any more
Function Expressions are created when the execution reaches them. That would happen only in the line `(*)`. Too late.
-**When a Function Declaration is made within a code block, it is visible everywhere inside that block. But not outside of it.**
+Another special feature of Function Declarations is their block scope.
-Sometimes that's handy to declare a local function only needed in that block alone. But that feature may also cause problems.
+**In strict mode, when a Function Declaration is within a code block, it's visible everywhere inside that block. But not outside of it.**
For instance, let's imagine that we need to declare a function `welcome()` depending on the `age` variable that we get during runtime. And then we plan to use it some time later.
-The code below doesn't work:
+If we use Function Declaration, it won't work as intended:
```js run
let age = prompt("What is your age?", 18);
@@ -292,7 +289,7 @@ if (age < 18) {
} else {
- function welcome() { // for age = 16, this "welcome" is never created
+ function welcome() {
alert("Greetings!");
}
}
@@ -309,7 +306,7 @@ What can we do to make `welcome` visible outside of `if`?
The correct approach would be to use a Function Expression and assign `welcome` to the variable that is declared outside of `if` and has the proper visibility.
-Now it works as intended:
+This code works as intended:
```js run
let age = prompt("What is your age?", 18);
@@ -350,113 +347,12 @@ welcome(); // ok now
```
-```smart header="When should you choose Function Declaration versus Function Expression?"
-As a rule of thumb, when we need to declare a function, the first to consider is Function Declaration syntax, the one we used before. It gives more freedom in how to organize our code, because we can call such functions before they are declared.
-
-It's also a little bit easier to look up `function f(…) {…}` in the code than `let f = function(…) {…}`. Function Declarations are more "eye-catching".
-
-...But if a Function Declaration does not suit us for some reason (we've seen an example above), then Function Expression should be used.
-```
-
-
-## Arrow functions [#arrow-functions]
-
-There's one more very simple and concise syntax for creating functions, that's often better than Function Expressions. It's called "arrow functions", because it looks like this:
-
-
-```js
-let func = (arg1, arg2, ...argN) => expression
-```
-
-...This creates a function `func` that has arguments `arg1..argN`, evaluates the `expression` on the right side with their use and returns its result.
-
-In other words, it's roughly the same as:
-
-```js
-let func = function(arg1, arg2, ...argN) {
- return expression;
-};
-```
-
-...But much more concise.
-
-Let's see an example:
-
-```js run
-let sum = (a, b) => a + b;
-
-/* The arrow function is a shorter form of:
-
-let sum = function(a, b) {
- return a + b;
-};
-*/
+```smart header="When to choose Function Declaration versus Function Expression?"
+As a rule of thumb, when we need to declare a function, the first to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared.
-alert( sum(1, 2) ); // 3
+That's also better for readability, as it's easier to look up `function f(…) {…}` in the code than `let f = function(…) {…};`. Function Declarations are more "eye-catching".
-```
-
-If we have only one argument, then parentheses can be omitted, making that even shorter:
-
-```js run
-// same as
-// let double = function(n) { return n * 2 }
-*!*
-let double = n => n * 2;
-*/!*
-
-alert( double(3) ); // 6
-```
-
-If there are no arguments, parentheses should be empty (but they should be present):
-
-```js run
-let sayHi = () => alert("Hello!");
-
-sayHi();
-```
-
-Arrow functions can be used in the same way as Function Expressions.
-
-For instance, here's the rewritten example with `welcome()`:
-
-```js run
-let age = prompt("What is your age?", 18);
-
-let welcome = (age < 18) ?
- () => alert('Hello') :
- () => alert("Greetings!");
-
-welcome(); // ok now
-```
-
-Arrow functions may appear unfamiliar and not very readable at first, but that quickly changes as the eyes get used to the structure.
-
-They are very convenient for simple one-line actions, when we're just too lazy to write many words.
-
-```smart header="Multiline arrow functions"
-
-The examples above took arguments from the left of `=>` and evaluated the right-side expression with them.
-
-Sometimes we need something a little bit more complex, like multiple expressions or statements. It is also possible, but we should enclose them in curly braces. Then use a normal `return` within them.
-
-Like this:
-
-```js run
-let sum = (a, b) => { // the curly brace opens a multiline function
- let result = a + b;
-*!*
- return result; // if we use curly braces, use return to get results
-*/!*
-};
-
-alert( sum(1, 2) ); // 3
-```
-
-```smart header="More to come"
-Here we praised arrow functions for brevity. But that's not all! Arrow functions have other interesting features. We'll return to them later in the chapter 253
or be less than -253
. As bigints are used in few special areas, we devote them a special chapter func`string`
. The function `func` is called automatically, receives the string and embedded expressions and can process them. You can read more about it in the [docs](mdn:/JavaScript/Reference/Template_literals#Tagged_template_literals). This is called "tagged templates". This feature makes it easier to wrap strings into custom templating or other functionality, but it is rarely used.
-
+Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`
. The function `func` is called automatically, receives the string and embedded expressions and can process them. This is called "tagged templates". This feature makes it easier to implement custom templating, but is rarely used in practice. You can read more about it in the [manual](mdn:/JavaScript/Reference/Template_literals#Tagged_templates).
## Special characters
-It is still possible to create multiline strings with single quotes by using a so-called "newline character", written as `\n`, which denotes a line break:
+It is still possible to create multiline strings with single and double quotes by using a so-called "newline character", written as `\n`, which denotes a line break:
```js run
let guestList = "Guests:\n * John\n * Pete\n * Mary";
@@ -60,39 +62,45 @@ let guestList = "Guests:\n * John\n * Pete\n * Mary";
alert(guestList); // a multiline list of guests
```
-For example, these two lines describe the same:
+For example, these two lines are equal, just written differently:
```js run
-alert( "Hello\nWorld" ); // two lines using a "newline symbol"
+let str1 = "Hello\nWorld"; // two lines using a "newline symbol"
// two lines using a normal newline and backticks
-alert( `Hello
-World` );
+let str2 = `Hello
+World`;
+
+alert(str1 == str2); // true
```
-There are other, less common "special" characters as well. Here's the list:
+There are other, less common "special" characters.
+
+Here's the full list:
| Character | Description |
|-----------|-------------|
-|`\b`|Backspace|
-|`\f`|Form feed|
|`\n`|New line|
-|`\r`|Carriage return|
+|`\r`|Carriage return: not used alone. Windows text files use a combination of two characters `\r\n` to represent a line break. |
+|`\'`, `\"`|Quotes|
+|`\\`|Backslash|
|`\t`|Tab|
-|`\uNNNN`|A unicode symbol with the hex code `NNNN`, for instance `\u00A9` -- is a unicode for the copyright symbol `©`. It must be exactly 4 hex digits. |
-|`\u{NNNNNNNN}`|Some rare characters are encoded with two unicode symbols, taking up to 4 bytes. This long unicode requires braces around it.|
+|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- kept for compatibility, not used nowadays. |
+|`\xXX`|Unicode character with the given hexadecimal Unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.|
+|`\uXXXX`|A Unicode symbol with the hex code `XXXX` in UTF-16 encoding, for instance `\u00A9` -- is a Unicode for the copyright symbol `©`. It must be exactly 4 hex digits. |
+|`\u{X…XXXXXX}` (1 to 6 hex characters)|A Unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two Unicode symbols, taking 4 bytes. This way we can insert long codes. |
-Examples with unicode:
+Examples with Unicode:
```js run
alert( "\u00A9" ); // ©
-alert( "\u{20331}" ); // 佫, a rare chinese hieroglyph (long unicode)
-alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long unicode)
+alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long Unicode)
+alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode)
```
All special characters start with a backslash character `\`. It is also called an "escape character".
-We would also use it if we want to insert a quote into the string.
+We might also use it if we wanted to insert a quote into the string.
For instance:
@@ -102,7 +110,7 @@ alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus!
As you can see, we have to prepend the inner quote by the backslash `\'`, because otherwise it would indicate the string end.
-Of course, that refers only to the quotes that are same as the enclosing ones. So, as a more elegant solution, we could switch to double quotes or backticks instead:
+Of course, only the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead:
```js run
alert( `I'm the Walrus!` ); // I'm the Walrus!
@@ -120,7 +128,6 @@ alert( `The backslash: \\` ); // The backslash: \
## String length
-
The `length` property has the string length:
```js run
@@ -189,7 +196,7 @@ For instance:
```js run
let str = 'Hi';
-str = 'h' + str[1]; // replace the string
+str = 'h' + str[1]; // replace the string
alert( str ); // hi
```
@@ -232,7 +239,7 @@ alert( str.indexOf('widget') ); // -1, not found, the search is case-sensitive
alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id)
```
-The optional second parameter allows us to search starting from the given position.
+The optional second parameter allows us to start searching from a given position.
For instance, the first occurrence of `"id"` is at position `1`. To look for the next occurrence, let's start the search from position `2`:
@@ -242,10 +249,8 @@ let str = 'Widget with id';
alert( str.indexOf('id', 2) ) // 12
```
-
If we're interested in all occurrences, we can run `indexOf` in a loop. Every new call is made with the position after the previous match:
-
```js run
let str = 'As sly as a fox, as strong as an ox';
@@ -305,10 +310,11 @@ if (str.indexOf("Widget") != -1) {
}
```
-````smart header="The bitwise NOT trick"
-One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation.
+#### The bitwise NOT trick
+
+One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation.
-For 32-bit integers the call `~n` means exactly the same as `-(n+1)` (due to IEEE-754 format).
+In practice, that means a simple thing: for 32-bit integers `~n` equals `-(n+1)`.
For instance:
@@ -321,9 +327,9 @@ alert( ~-1 ); // 0, the same as -(-1+1)
*/!*
```
-As we can see, `~n` is zero only if `n == -1`.
+As we can see, `~n` is zero only if `n == -1` (that's for any 32-bit signed integer `n`).
-So, the test `if ( ~str.indexOf("...") )` is truthy that the result of `indexOf` is not `-1`. In other words, when there is a match.
+So, the test `if ( ~str.indexOf("...") )` is truthy only if the result of `indexOf` is not `-1`. In other words, when there is a match.
People use it to shorten `indexOf` checks:
@@ -338,7 +344,10 @@ if (~str.indexOf("Widget")) {
It is usually not recommended to use language features in a non-obvious way, but this particular trick is widely used in old code, so we should understand it.
Just remember: `if (~str.indexOf(...))` reads as "if found".
-````
+
+To be precise though, as big numbers are truncated to 32 bits by `~` operator, there exist other numbers that give `0`, the smallest is `~4294967295=0`. That makes such check correct only if a string is not that long.
+
+Right now we can see this trick only in the old code, as modern JavaScript provides `.includes` method (see below).
### includes, startsWith, endsWith
@@ -355,15 +364,15 @@ alert( "Hello".includes("Bye") ); // false
The optional second argument of `str.includes` is the position to start searching from:
```js run
-alert( "Midget".includes("id") ); // true
-alert( "Midget".includes("id", 3) ); // false, from position 3 there is no "id"
+alert( "Widget".includes("id") ); // true
+alert( "Widget".includes("id", 3) ); // false, from position 3 there is no "id"
```
The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say:
```js run
alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
-alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
+alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
```
## Getting a substring
@@ -385,7 +394,7 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
```js run
let str = "st*!*ringify*/!*";
- alert( str.slice(2) ); // ringify, from the 2nd position till the end
+ alert( str.slice(2) ); // 'ringify', from the 2nd position till the end
```
Negative values for `start/end` are also possible. They mean the position is counted from the string end:
@@ -394,10 +403,9 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
let str = "strin*!*gif*/!*y";
// start at the 4th position from the right, end at the 1st from the right
- alert( str.slice(-4, -1) ); // gif
+ alert( str.slice(-4, -1) ); // 'gif'
```
-
`str.substring(start [, end])`
: Returns the part of the string *between* `start` and `end`.
@@ -405,7 +413,6 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
For instance:
-
```js run
let str = "st*!*ring*/!*ify";
@@ -421,7 +428,6 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
Negative arguments are (unlike slice) not supported, they are treated as `0`.
-
`str.substr(start [, length])`
: Returns the part of the string from `start`, with the given `length`.
@@ -429,14 +435,14 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
```js run
let str = "st*!*ring*/!*ify";
- alert( str.substr(2, 4) ); // ring, from the 2nd position get 4 characters
+ alert( str.substr(2, 4) ); // 'ring', from the 2nd position get 4 characters
```
The first argument may be negative, to count from the end:
```js run
let str = "strin*!*gi*/!*fy";
- alert( str.substr(-4, 2) ); // gi, from the 4th position get 2 characters
+ alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters
```
Let's recap these methods to avoid any confusion:
@@ -447,11 +453,10 @@ Let's recap these methods to avoid any confusion:
| `substring(start, end)` | between `start` and `end` | negative values mean `0` |
| `substr(start, length)` | from `start` get `length` characters | allows negative `start` |
-
```smart header="Which one to choose?"
All of them can do the job. Formally, `substr` has a minor drawback: it is described not in the core JavaScript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere.
-The author finds themself using `slice` almost all the time.
+Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. So, it's enough to remember solely `slice` of these three methods.
```
## Comparing strings
@@ -494,7 +499,7 @@ All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). Th
alert( String.fromCodePoint(90) ); // Z
```
- We can also add unicode characters by their codes using `\u` followed by the hex code:
+ We can also add Unicode characters by their codes using `\u` followed by the hex code:
```js run
// 90 is 5a in hexadecimal system
@@ -514,31 +519,30 @@ alert( str );
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ
```
-See? Capital characters go first, then a few special ones, then lowercase characters.
+See? Capital characters go first, then a few special ones, then lowercase characters, and `Ö` near the end of the output.
Now it becomes obvious why `a > Z`.
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. The same-looking letter may be located differently in different alphabets.
+The "right" algorithm to do string comparisons is more complex than it may seem, because alphabets are different for different languages.
So, the browser needs to know the language to compare.
-Luckily, all modern browsers (IE10- requires the additional library [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf).
+Luckily, all modern browsers (IE10- requires the additional library [Intl.js](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf).
It provides a special method to compare strings in different languages, following their rules.
-The call [str.localeCompare(str2)](mdn:js/String/localeCompare):
+The call [str.localeCompare(str2)](mdn:js/String/localeCompare) returns an integer indicating whether `str` is less, equal or greater than `str2` according to the language rules:
-- Returns `1` if `str` is greater than `str2` according to the language rules.
-- Returns `-1` if `str` is less than `str2`.
-- Returns `0` if they are equal.
+- Returns a negative number if `str` is less than `str2`.
+- Returns a positive number if `str` is greater than `str2`.
+- Returns `0` if they are equivalent.
For instance:
@@ -546,7 +550,7 @@ For instance:
alert( 'Österreich'.localeCompare('Zealand') ); // -1
```
-This method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), which allows it to specify the language (by default taken from the environment) and setup additional rules like case sensitivity or should `"a"` and `"á"` be treated as the same etc.
+This method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), which allows it to specify the language (by default taken from the environment, letter order depends on the language) and setup additional rules like case sensitivity or should `"a"` and `"á"` be treated as the same etc.
## Internals, Unicode
@@ -558,7 +562,7 @@ You can skip the section if you don't plan to support them.
### Surrogate pairs
-Most symbols have a 2-byte code. Letters in most european languages, numbers, and even most hieroglyphs, have a 2-byte representation.
+All frequently used characters have 2-byte codes. Letters in most european languages, numbers, and even most hieroglyphs, have a 2-byte representation.
But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair".
@@ -567,7 +571,7 @@ The length of such symbols is `2`:
```js run
alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X
alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY
-alert( '𩷶'.length ); // 2, a rare chinese hieroglyph
+alert( '𩷶'.length ); // 2, a rare Chinese hieroglyph
```
Note that surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language!
@@ -576,7 +580,7 @@ We actually have a single symbol in each of the strings above, but the `length`
`String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs.
-But, for instance, getting a symbol can be tricky, because surrogate pairs are treated as two characters:
+Getting a symbol can be tricky, because surrogate pairs are treated as two characters:
```js run
alert( '𝒳'[0] ); // strange symbols...
@@ -604,7 +608,7 @@ In many languages there are symbols that are composed of the base character with
For instance, the letter `a` can be the base character for: `àáâäãåā`. Most common "composite" character have their own code in the UTF-16 table. But not all of them, because there are too many possible combinations.
-To support arbitrary compositions, UTF-16 allows us to use several unicode characters. The base character and one or many "mark" characters that "decorate" it.
+To support arbitrary compositions, UTF-16 allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it.
For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ.
@@ -622,18 +626,20 @@ For example:
alert( 'S\u0307\u0323' ); // Ṩ
```
-This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different unicode compositions.
+This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions.
For instance:
```js run
-alert( 'S\u0307\u0323' ); // Ṩ, S + dot above + dot below
-alert( 'S\u0323\u0307' ); // Ṩ, S + dot below + dot above
+let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below
+let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above
-alert( 'S\u0307\u0323' == 'S\u0323\u0307' ); // false
+alert( `s1: ${s1}, s2: ${s2}` );
+
+alert( s1 == s2 ); // false though the characters look identical (?!)
```
-To solve this, there exists a "unicode normalization" algorithm that brings each string to the single "normal" form.
+To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form.
It is implemented by [str.normalize()](mdn:js/String/normalize).
@@ -649,16 +655,15 @@ alert( "S\u0307\u0323".normalize().length ); // 1
alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
```
-In reality, this is not always the case. The reason being that the symbol `Ṩ` is "common enough", so UTF-16 creators included it in the main table and gave it the code.
+In reality, this is not always the case. The reason being that the symbol `Ṩ` is "common enough", so UTF-16 creators included it in the main table and gave it the code.
If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough.
-
## Summary
-- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions.
+- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions `${…}`.
- Strings in JavaScript are encoded using UTF-16.
-- We can use special characters like `\n` and insert letters by their unicode using `\u...`.
+- We can use special characters like `\n` and insert letters by their Unicode using `\u...`.
- To get a character, use: `[]`.
- To get a substring, use: `slice` or `substring`.
- To lowercase/uppercase a string, use: `toLowerCase/toUpperCase`.
@@ -669,6 +674,6 @@ There are several other helpful methods in strings:
- `str.trim()` -- removes ("trims") spaces from the beginning and end of the string.
- `str.repeat(n)` -- repeats the string `n` times.
-- ...and more. See the [manual](mdn:js/String) for details.
+- ...and more to be found in the [manual](mdn:js/String).
-Strings also have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later.
+Strings also have methods for doing search/replace with regular expressions. But that's big topic, so it's explained in a separate tutorial section handler
is called on this input:
+
+
+Debounced function debounce(handler, 1000)
is called on this input:
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
index 4f5867ded..83e75f315 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
@@ -1,28 +1,13 @@
```js demo
-function debounce(f, ms) {
-
- let isCooldown = false;
-
+function debounce(func, ms) {
+ let timeout;
return function() {
- if (isCooldown) return;
-
- f.apply(this, arguments);
-
- isCooldown = true;
-
- setTimeout(() => isCooldown = false, ms);
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(this, arguments), ms);
};
-
}
-```
-
-A call to `debounce` returns a wrapper. There may be two states:
-- `isCooldown = false` -- ready to run.
-- `isCooldown = true` -- waiting for the timeout.
-
-In the first call `isCooldown` is falsy, so the call proceeds, and the state changes to `true`.
+```
-While `isCooldown` is true, all other calls are ignored.
+A call to `debounce` returns a wrapper. When called, it schedules the original function call after given `ms` and cancels the previous such timeout.
-Then `setTimeout` reverts it to `false` after the given delay.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
index 466c6bc3f..5b0fcc5f8 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
@@ -4,21 +4,48 @@ importance: 5
# Debounce decorator
-The result of `debounce(f, ms)` decorator should be a wrapper that passes the call to `f` at maximum once per `ms` milliseconds.
+The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments.
-In other words, when we call a "debounced" function, it guarantees that all other future in the closest `ms` milliseconds will be ignored.
+In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`).
-For instance:
+For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`.
-```js no-beautify
-let f = debounce(alert, 1000);
+Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call.
-f(1); // runs immediately
-f(2); // ignored
+
-setTimeout( () => f(3), 100); // ignored ( only 100 ms passed )
-setTimeout( () => f(4), 1100); // runs
-setTimeout( () => f(5), 1500); // ignored (less than 1000 ms from the last run)
+...And it will get the arguments of the very last call, other calls are ignored.
+
+Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)):
+
+```js
+let f = _.debounce(alert, 1000);
+
+f("a");
+setTimeout( () => f("b"), 200);
+setTimeout( () => f("c"), 500);
+// debounced function waits 1000ms after the last call and then runs: alert("c")
+```
+
+Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished.
+
+There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result.
+
+In a web-browser, we can setup an event handler -- a function that's called on every change of an input field. Normally, an event handler is called very often, for every typed key. But if we `debounce` it by 1000ms, then it will be only called once, after 1000ms after the last input.
+
+```online
+
+In this live example, the handler puts the result into a box below, try it:
+
+[iframe border=1 src="debounce" height=200]
+
+See? The second input calls the debounced function, so its content is processed after 1000ms from the last input.
```
-In practice `debounce` is useful for functions that retrieve/update something when we know that nothing new can be done in such a short period of time, so it's better not to waste resources.
\ No newline at end of file
+So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else.
+
+It waits the given time after the last call, and then runs its function, that can process the result.
+
+The task is to implement `debounce` decorator.
+
+Hint: that's just a few lines if you think about it :)
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js
index 5339c8d11..e671438f6 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js
@@ -7,8 +7,8 @@ describe("throttle(f, 1000)", function() {
}
before(function() {
- f1000 = throttle(f, 1000);
this.clock = sinon.useFakeTimers();
+ f1000 = throttle(f, 1000);
});
it("the first call runs now", function() {
@@ -44,4 +44,20 @@ describe("throttle(f, 1000)", function() {
this.clock.restore();
});
-});
\ No newline at end of file
+});
+
+describe('throttle', () => {
+
+ it('runs a forwarded call once', done => {
+ let log = '';
+ const f = str => log += str;
+ const f10 = throttle(f, 10);
+ f10('once');
+
+ setTimeout(() => {
+ assert.equal(log, 'once');
+ done();
+ }, 20);
+ });
+
+});
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 c844016d3..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) {
@@ -33,7 +32,7 @@ function throttle(func, ms) {
A call to `throttle(func, ms)` returns `wrapper`.
1. During the first call, the `wrapper` just runs `func` and sets the cooldown state (`isThrottled = true`).
-2. In this state all calls memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call.
-3. ...Then after `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`). And if we had ignored calls, then `wrapper` is executed with last memorized arguments and context.
+2. In this state all calls are memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call.
+3. After `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`) and, if we had ignored calls, `wrapper` is executed with the last memorized arguments and context.
The 3rd step runs not `func`, but `wrapper`, because we not only need to execute `func`, but once again enter the cooldown state and setup the timeout to reset it.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
index 8dd77368d..6df7af132 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
@@ -4,35 +4,40 @@ importance: 5
# Throttle decorator
-Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper, passing the call to `f` at maximum once per `ms` milliseconds. Those calls that fall into the "cooldown" period, are ignored.
+Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper.
-**The difference with `debounce` -- if an ignored call is the last during the cooldown, then it executes at the end of the delay.**
+When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds.
+
+The difference with debounce is that it's completely different decorator:
+- `debounce` runs the function once after the "cooldown" period. Good for processing the final result.
+- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often.
+
+In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds.
Let's check the real-life application to better understand that requirement and to see where it comes from.
**For instance, we want to track mouse movements.**
-In browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms).
-
-**The tracking function should update some information on the web-page.**
+In a browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms).
+**We'd like to update some information on the web-page when the pointer moves.**
-Updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in making it more often than once per 100ms.
+...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms.
-So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but `update()` will be called at maximum once per 100ms.
+So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but forward the call to `update()` at maximum once per 100ms.
Visually, it will look like this:
-1. For the first mouse movement the decorated variant passes the call to `update`. That's important, the user sees our reaction to their move immediately.
+1. For the first mouse movement the decorated variant immediately passes the call to `update`. That's important, the user sees our reaction to their move immediately.
2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls.
-3. At the end of `100ms` -- one more `update` happens with the last coordinates.
-4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, perhaps the most important, the final mouse coordinates are processed.
+3. At the end of `100ms` -- one more `update` happens with the last coordinates.
+4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, quite important, the final mouse coordinates are processed.
A code example:
```js
function f(a) {
- console.log(a)
-};
+ console.log(a);
+}
// f1000 passes calls to f at maximum once per 1000 ms
let f1000 = throttle(f, 1000);
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 600f0ea1b..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
@@ -6,9 +6,9 @@ JavaScript gives exceptional flexibility when dealing with functions. They can b
Let's say we have a function `slow(x)` which is CPU-heavy, but its results are stable. In other words, for the same `x` it always returns the same result.
-If the function is called often, we may want to cache (remember) the results for different `x` to avoid spending extra-time on recalculations.
+If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations.
-But instead of adding that functionality into `slow()` we'll create a wrapper. As we'll see, there are many benefits of doing so.
+But instead of adding that functionality into `slow()` we'll create a wrapper function, that adds caching. As we'll see, there are many benefits of doing so.
Here's the code, and explanations follow:
@@ -23,24 +23,24 @@ function cachingDecorator(func) {
let cache = new Map();
return function(x) {
- if (cache.has(x)) { // if the result is in the map
- return cache.get(x); // return it
+ if (cache.has(x)) { // if there's such key in cache
+ return cache.get(x); // read the result from it
}
- let result = func(x); // otherwise call func
+ let result = func(x); // otherwise call func
- cache.set(x, result); // and cache (remember) the result
+ cache.set(x, result); // and cache (remember) the result
return result;
};
}
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.
@@ -49,21 +49,18 @@ The idea is that we can call `cachingDecorator` for any function, and it will re
By separating caching from the main function code we also keep the main code simpler.
-Now let's get into details of how it works.
-
The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic:

-As we can see, the wrapper returns the result of `func(x)` "as is". From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior.
+From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior.
To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself:
- The `cachingDecorator` is reusable. We can apply it to another function.
-- The caching logic is separate, it did not increase the complexity of `slow` itself (if there were any).
+- The caching logic is separate, it did not increase the complexity of `slow` itself (if there was any).
- We can combine multiple decorators if needed (other decorators will follow).
-
## Using "func.call" for the context
The caching decorator mentioned above is not suited to work with object methods.
@@ -78,7 +75,7 @@ let worker = {
},
slow(x) {
- // actually, there can be a scary CPU-heavy task here
+ // scary CPU-heavy task here
alert("Called with " + x);
return x * this.someMethod(); // (*)
}
@@ -152,8 +149,8 @@ let user = { name: "John" };
let admin = { name: "Admin" };
// use call to pass different objects as "this"
-sayHi.call( user ); // this = John
-sayHi.call( admin ); // this = Admin
+sayHi.call( user ); // John
+sayHi.call( admin ); // Admin
```
And here we use `call` to call `say` with the given context and phrase:
@@ -170,10 +167,8 @@ let user = { name: "John" };
say.call( user, "Hello" ); // John: Hello
```
-
In our case, we can use `call` in the wrapper to pass the context to the original function:
-
```js run
let worker = {
someMethod() {
@@ -214,7 +209,7 @@ To make it all clear, let's see more deeply how `this` is passed along:
2. So when `worker.slow(2)` is executed, the wrapper gets `2` as an argument and `this=worker` (it's the object before dot).
3. Inside the wrapper, assuming the result is not yet cached, `func.call(this, x)` passes the current `this` (`=worker`) and the current argument (`=2`) to the original method.
-## Going multi-argument with "func.apply"
+## Going multi-argument
Now let's make `cachingDecorator` even more universal. Till now it was working only with single-argument functions.
@@ -231,9 +226,7 @@ let worker = {
worker.slow = cachingDecorator(worker.slow);
```
-We have two tasks to solve here.
-
-First is how to use both arguments `min` and `max` for the key in `cache` map. Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key.
+Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key.
There are many solutions possible:
@@ -241,85 +234,11 @@ There are many solutions possible:
2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`.
3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many.
-
For many practical applications, the 3rd variant is good enough, so we'll stick to it.
-The second task to solve is how to pass many arguments to `func`. Currently, the wrapper `function(x)` assumes a single argument, and `func.call(this, x)` passes it.
-
-Here we can use another built-in method [func.apply](mdn:js/Function/apply).
-
-The syntax is:
-
-```js
-func.apply(context, args)
-```
-
-It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments.
-
-
-For instance, these two calls are almost the same:
-
-```js
-func(1, 2, 3);
-func.apply(context, [1, 2, 3])
-```
-
-Both run `func` giving it arguments `1,2,3`. But `apply` also sets `this=context`.
-
-For instance, here `say` is called with `this=user` and `messageData` as a list of arguments:
-
-```js run
-function say(time, phrase) {
- alert(`[${time}] ${this.name}: ${phrase}`);
-}
-
-let user = { name: "John" };
-
-let messageData = ['10:00', 'Hello']; // become time and phrase
-
-*!*
-// user becomes this, messageData is passed as a list of arguments (time, phrase)
-say.apply(user, messageData); // [10:00] John: Hello (this=user)
-*/!*
-```
-
-The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them.
-
-We already know the spread operator `...` from the chapter