(253-1)
(that's `9007199254740991`), or less than -(253-1)
for negatives.
+
+To be really precise, the "number" type can store larger integers (up to 1.7976931348623157 * 10308
), but outside of the safe integer range ±(253-1)
there'll be a precision error, because not all digits fit into the fixed 64-bit storage. So an "approximate" value may be stored.
+
+For example, these two numbers (right above the safe range) are the same:
+
+```js
+console.log(9007199254740991 + 1); // 9007199254740992
+console.log(9007199254740991 + 2); // 9007199254740992
+```
+
+So to say, all odd integers greater than (253-1)
can't be stored at all in the "number" type.
+
+For most purposes ±(253-1)
range is quite enough, but sometimes we need the entire range of really big integers, 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`
.
+<<<<<<< HEAD
Dupli i pojedinačni navodnici su "jednostavni" citati. Nema razlike među njima u JavaScript-u.
+=======
+Double and single quotes are "simple" quotes. There's practically no difference between them in JavaScript.
+>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b
Zatvoreni jednostruki navodnici su navodnici "proširene funkcionalnosti". Omogućuju nam da uklopimo varijable i izraze u niz omotajući ih, na primer, „$ {…}“
@@ -101,13 +160,20 @@ alert( "Rezultat je ${1 + 2}" ); // Rezultat je ${1 + 2} (Dupli navodnici ne rad
Detaljnije ćemo prikazati stringove u ovom poglavlju ±(253-1)
.
+ - `bigint` 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`.
+ - `symbol` for unique identifiers.
+- And one non-primitive data type:
+ - `object` for more complex data structures.
The `typeof` operator allows us to see which type is stored in a variable.
-- Two forms: `typeof x` or `typeof(x)`.
+- Usually used as `typeof x`, but `typeof(x)` is also possible.
- Returns a string with the name of the type, like `"string"`.
- For `null` returns `"object"` -- this is an error in the language, it's not actually an object.
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 79%
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 8ba414e9c..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
@@ -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.
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 70%
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..329556141 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 and false
| `1` and `0` |
-| `string` | Whitespaces from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. |
+| `string` | Whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. |
Examples:
@@ -81,20 +83,9 @@ alert( Number(false) ); // 0
Please note that `null` and `undefined` behave differently here: `null` becomes zero while `undefined` becomes `NaN`.
-````smart header="Addition '+' concatenates strings"
-Almost all mathematical operations convert values to numbers. A notable exception is addition `+`. If one of the added values is a string, the other one is also converted to a string.
-
-Then, it concatenates (joins) them:
-
-```js run
-alert( 1 + '2' ); // '12' (string to the right)
-alert( '1' + 2 ); // '12' (string to the left)
-```
-
-This only happens when at least one of the arguments is a string. Otherwise, values are converted to numbers.
-````
+Most mathematical operators also perform such conversion, we'll see that in the next chapter.
-## ToBoolean
+## Boolean Conversion
Boolean conversion is the simplest one.
@@ -124,14 +115,13 @@ alert( Boolean(" ") ); // spaces, also true (any non-empty string is true)
```
````
-
## Summary
The three most widely used type conversions are to string, to number, and to boolean.
-**`ToString`** -- Occurs when we output something. Can be performed with `String(value)`. The conversion to string is usually obvious for primitive values.
+**`String Conversion`** -- Occurs when we output something. Can be performed with `String(value)`. The conversion to string is usually obvious for primitive values.
-**`ToNumber`** -- Occurs in math operations. Can be performed with `Number(value)`.
+**`Numeric Conversion`** -- Occurs in math operations. Can be performed with `Number(value)`.
The conversion follows the rules:
@@ -140,9 +130,9 @@ The conversion follows the rules:
|`undefined`|`NaN`|
|`null`|`0`|
|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`. |
+| `string` | The string is read "as is", whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) 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/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..7370b66af 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 72%
rename from 1-js/02-first-steps/07-operators/article.md
rename to 1-js/02-first-steps/08-operators/article.md
index 965cde936..d52c37a17 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,62 @@ 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, the remainder of 5 divided by 2
+alert( 8 % 3 ); // 2, the remainder of 8 divided by 3
+alert( 8 % 4 ); // 0, the remainder of 8 divided by 4
+```
+
+### 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 the features of JavaScript operators that are beyond school arithmetics.
Usually, the plus operator `+` sums numbers.
@@ -41,7 +92,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 +101,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"`.
+
+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.
-For instance, subtraction and division:
+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 +
@@ -133,26 +190,27 @@ Parentheses override any precedence, so if we're not satisfied with the default
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 | `/` |
-| 13 | addition | `+` |
-| 13 | subtraction | `-` |
+| 14 | unary plus | `+` |
+| 14 | unary negation | `-` |
+| 13 | exponentiation | `**` |
+| 12 | multiplication | `*` |
+| 12 | division | `/` |
+| 11 | addition | `+` |
+| 11 | subtraction | `-` |
| ... | ... | ... |
-| 3 | assignment | `=` |
+| 2 | 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 `14` which is higher than the `11` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition.
## Assignment
-Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `3`.
+Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `2`.
That's why, when we assign a variable, like `x = 2 * 2 + 1`, the calculations are done first and then the `=` is evaluated, storing the result in `x`.
@@ -162,24 +220,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,49 +244,74 @@ alert( c ); // 0
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 JavaScript libraries, but shouldn't write anything like that ourselves. Such tricks definitely don't make code clearer or readable.
-````
-
-## Remainder %
+Funny code, isn't it? We should understand how it works, because sometimes we see it in JavaScript libraries.
-The remainder operator `%`, despite its appearance, is not related to percents.
+Although, please don't write the code like that. Such tricks definitely don't make code clearer or readable.
-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
+```
+
+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.
+
+Once again, for the purposes of readability it's better to split such code into few lines:
+
+```js
+c = 2 + 2;
+b = c;
+a = c;
```
+That's easier to read, especially when eye-scanning the code fast.
-## Exponentiation **
+## Modify-in-place
-The exponentiation operator `**` is a recent addition to the language.
+We often need to apply an operator to a variable and store the new result in that same variable.
-For a natural number `b`, the result of `a ** b` is `a` multiplied by itself `b` times.
+For example:
-For instance:
+```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; // right part evaluated first, same as n *= 8
+
+alert( n ); // 16
```
## Increment/decrement
-
+
Increasing or decreasing a number by one is among the most common numerical operations.
@@ -368,41 +438,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_operators) chapter on MDN when a need arises.
## Comma
diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md
similarity index 75%
rename from 1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md
rename to 1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md
index 6437b512e..632b1cf4e 100644
--- a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md
+++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md
@@ -13,8 +13,8 @@ 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"`.
+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`.
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 d889b1328..a69317fee 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`.
@@ -192,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 a4d943245..4305584fa 100644
--- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md
+++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md
@@ -6,7 +6,7 @@ importance: 2
Using the `if..else` construct, write the code which asks: 'What is the "official" name of JavaScript?'
-If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "Didn't know? ECMAScript!"
+If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "You don't know? ECMAScript!"

diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md
index 30287ccba..82e8800b9 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.
@@ -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
@@ -181,9 +181,9 @@ alert( message );
It may be difficult at first to grasp what's going on. But after a closer look, we can see that it's just an ordinary sequence of tests:
1. The first question mark checks whether `age < 3`.
-2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon '":"', checking `age < 18`.
-3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon '":"', checking `age < 100`.
-4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon '":"', returning `'What an unusual age!'`.
+2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon ":", checking `age < 18`.
+3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon ":", checking `age < 100`.
+4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon ":", returning `'What an unusual age!'`.
Here's how this looks using `if..else`:
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/3-alert-1-null-2/solution.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
index 5c2455ef4..368b59409 100644
--- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
+++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
@@ -1,6 +1,6 @@
The answer: `null`, because it's the first falsy value from the list.
```js run
-alert( 1 && null && 2 );
+alert(1 && null && 2);
```
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 a30db7aae..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) {
+ } 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/article.md b/1-js/02-first-steps/11-logical-operators/article.md
index 25f8ff7f5..78c4fd2f1 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);
+ The 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)
@@ -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..0b2f092ab
--- /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. For brevity, we'll say that a value 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 example, here we show `user` if its value isn't `null/undefined`, otherwise `Anonymous`:
+
+```js run
+let user;
+
+alert(user ?? "Anonymous"); // Anonymous (user is undefined)
+```
+
+Here's the example with `user` assigned to a name:
+
+```js run
+let user = "John";
+
+alert(user ?? "Anonymous"); // John (user is not null/undefined)
+```
+
+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 fill in the corresponding values.
+
+We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are `null/undefined`.
+
+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's been there 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 the same as `||`. They both equal `3` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table).
+
+That means that, just like `||`, the nullish coalescing operator `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`.
+
+So we may need to add parentheses in expressions like this:
+
+```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 this way (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 81f7c1666..d1b749888 100644
--- a/1-js/02-first-steps/12-while-for/article.md
+++ b/1-js/02-first-steps/13-while-for/article.md
@@ -6,6 +6,19 @@ For example, outputting goods from a list one after another or just running the
*Loops* are a way to repeat the same code multiple times.
+```smart header="The for..of and for..in loops"
+A small announcement for advanced readers.
+
+This article covers only basic loops: `while`, `do..while` and `for(..;..;..)`.
+
+If you came to this article searching for other types of loops, here are the pointers:
+
+- See [for..in](info:object#forin) to loop over object properties.
+- See [for..of](info:array#loops) and [iterables](info:iterable) for looping over arrays and iterable objects.
+
+Otherwise, please read on.
+```
+
## The "while" loop
The `while` loop has the following syntax:
@@ -106,10 +119,10 @@ 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:
@@ -162,10 +175,8 @@ for (i = 0; i < 3; i++) { // use an existing variable
alert(i); // 3, visible, because declared outside of the loop
```
-
````
-
### Skipping parts
Any part of `for` can be skipped.
@@ -212,7 +223,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) {
@@ -256,7 +267,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) {
@@ -268,7 +279,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 '?'"
@@ -286,7 +297,6 @@ if (i > 5) {
...and rewrite it using a question mark:
-
```js no-beautify
(i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here
```
@@ -300,7 +310,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++) {
@@ -318,9 +328,10 @@ 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
labelName: for (...) {
...
@@ -342,6 +353,7 @@ The `break (253-1)
or be less than -(253-1)
, as we mentioned earlier in the 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 feature is called "tagged templates", it's rarely seen, but you can read about it in the MDN: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates).
## Special characters
@@ -59,10 +59,10 @@ It is still possible to create multiline strings with single and double quotes b
```js run
let guestList = "Guests:\n * John\n * Pete\n * Mary";
-alert(guestList); // a multiline list of guests
+alert(guestList); // a multiline list of guests, same as above
```
-For example, these two lines are equal, just written differently:
+As a simpler example, these two lines are equal, just written differently:
```js run
let str1 = "Hello\nWorld"; // two lines using a "newline symbol"
@@ -74,33 +74,26 @@ World`;
alert(str1 == str2); // true
```
-There are other, less common "special" characters.
-
-Here's the full list:
+There are other, less common special characters:
| Character | Description |
|-----------|-------------|
|`\n`|New line|
-|`\r`|Carriage return: not used alone. Windows text files use a combination of two characters `\r\n` to represent a line break. |
-|`\'`, `\"`|Quotes|
+|`\r`|In Windows text files a combination of two characters `\r\n` represents a new break, while on non-Windows OS it's just `\n`. That's for historical reasons, most Windows software also understands `\n`. |
+|`\'`, `\"`, \\`
|Quotes|
|`\\`|Backslash|
|`\t`|Tab|
-|`\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. |
+|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- mentioned for completeness, coming from old times, not used nowadays (you can forget them right now). |
+
+As you can see, all special characters start with a backslash character `\`. It is also called an "escape character".
-Examples with unicode:
+Because it's so special, if we need to show an actual backslash `\` within the string, we need to double it:
```js run
-alert( "\u00A9" ); // ©
-alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long unicode)
-alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long unicode)
+alert( `The backslash: \\` ); // The backslash: \
```
-All special characters start with a backslash character `\`. It is also called an "escape character".
-
-We might also use it if we wanted to insert a quote into the string.
+So-called "escaped" quotes `\'`, `\"`, \\`
are used to insert a quote into the same-quoted string.
For instance:
@@ -110,21 +103,13 @@ 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, only to 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:
+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!
+alert( "I'm the Walrus!" ); // I'm the Walrus!
```
-Note that the backslash `\` serves for the correct reading of the string by JavaScript, then disappears. The in-memory string has no `\`. You can clearly see that in `alert` from the examples above.
-
-But what if we need to show an actual backslash `\` within the string?
-
-That's possible, but we need to double it like `\\`:
-
-```js run
-alert( `The backslash: \\` ); // The backslash: \
-```
+Besides these special characters, there's also a special notation for Unicode codes `\u…`, it's rarely used and is covered in the optional chapter about [Unicode](info:unicode).
## String length
@@ -139,33 +124,36 @@ Note that `\n` is a single "special" character, so the length is indeed `3`.
```warn header="`length` is a property"
People with a background in some other languages sometimes mistype by calling `str.length()` instead of just `str.length`. That doesn't work.
-Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it.
+Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. Not `.length()`, but `.length`.
```
## Accessing characters
-To get a character at position `pos`, use square brackets `[pos]` or call the method [str.charAt(pos)](mdn:js/String/charAt). The first character starts from the zero position:
+To get a character at position `pos`, use square brackets `[pos]` or call the method [str.at(pos)](mdn:js/String/at). The first character starts from the zero position:
```js run
let str = `Hello`;
// the first character
alert( str[0] ); // H
-alert( str.charAt(0) ); // H
+alert( str.at(0) ); // H
// the last character
alert( str[str.length - 1] ); // o
+alert( str.at(-1) );
```
-The square brackets are a modern way of getting a character, while `charAt` exists mostly for historical reasons.
+As you can see, the `.at(pos)` method has a benefit of allowing negative position. If `pos` is negative, then it's counted from the end of the string.
-The only difference between them is that if no character is found, `[]` returns `undefined`, and `charAt` returns an empty string:
+So `.at(-1)` means the last character, and `.at(-2)` is the one before it, etc.
+
+The square brackets always return `undefined` for negative indexes, for instance:
```js run
let str = `Hello`;
-alert( str[1000] ); // undefined
-alert( str.charAt(1000) ); // '' (an empty string)
+alert( str[-2] ); // undefined
+alert( str.at(-2) ); // l
```
We can also iterate over characters using `for..of`:
@@ -214,7 +202,7 @@ alert( 'Interface'.toLowerCase() ); // interface
Or, if we want a single character lowercased:
-```js
+```js run
alert( 'Interface'[0].toLowerCase() ); // 'i'
```
@@ -239,7 +227,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`:
@@ -310,45 +298,6 @@ if (str.indexOf("Widget") != -1) {
}
```
-#### 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.
-
-In practice, that means a simple thing: for 32-bit integers `~n` equals `-(n+1)`.
-
-For instance:
-
-```js run
-alert( ~2 ); // -3, the same as -(2+1)
-alert( ~1 ); // -2, the same as -(1+1)
-alert( ~0 ); // -1, the same as -(0+1)
-*!*
-alert( ~-1 ); // 0, the same as -(-1+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 only if the result of `indexOf` is not `-1`. In other words, when there is a match.
-
-People use it to shorten `indexOf` checks:
-
-```js run
-let str = "Widget";
-
-if (~str.indexOf("Widget")) {
- alert( 'Found it!' ); // works
-}
-```
-
-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 is correct only if a string is not that long.
-
-Right now we can see this trick only in the old code, as modern JavaScript provides `.includes` method (see below).
-
### includes, startsWith, endsWith
The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` contains `substr` within.
@@ -371,8 +320,8 @@ 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( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Widget" starts with "Wid"
+alert( "Wid*!*get*/!*".endsWith("get") ); // true, "Widget" ends with "get"
```
## Getting a substring
@@ -394,7 +343,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:
@@ -403,13 +352,13 @@ 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`.
+: Returns the part of the string *between* `start` and `end` (not including `end`).
- This is almost the same as `slice`, but it allows `start` to be greater than `end`.
+ This is almost the same as `slice`, but it allows `start` to be greater than `end` (in this case it simply swaps `start` and `end` values).
For instance:
@@ -435,28 +384,32 @@ 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
```
+ This method resides in the [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) of the language specification. It means that only browser-hosted Javascript engines should support it, and it's not recommended to use it. In practice, it's supported everywhere.
+
Let's recap these methods to avoid any confusion:
| method | selects... | negatives |
|--------|-----------|-----------|
| `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives |
-| `substring(start, end)` | between `start` and `end` | negative values mean `0` |
+| `substring(start, end)` | between `start` and `end` (not including `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.
-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.
+Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write.
+
+So, for practical use it's enough to remember only `slice`.
```
## Comparing strings
@@ -479,17 +432,18 @@ Although, there are some oddities.
This may lead to strange results if we sort these country names. Usually people would expect `Zealand` to come after `Österreich` in the list.
-To understand what happens, let's review the internal representation of strings in JavaScript.
+To understand what happens, we should be aware that strings in Javascript are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code.
-All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. There are special methods that allow to get the character for the code and back.
+There are special methods that allow to get the character for the code and back:
`str.codePointAt(pos)`
-: Returns the code for the character at position `pos`:
+: Returns a decimal number representing the code for the character at position `pos`:
```js run
// different case letters have different codes
- alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90
+ alert( "z".codePointAt(0) ); // 122
+ alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value)
```
`String.fromCodePoint(code)`
@@ -497,13 +451,7 @@ All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). Th
```js run
alert( String.fromCodePoint(90) ); // Z
- ```
-
- We can also add unicode characters by their codes using `\u` followed by the hex code:
-
- ```js run
- // 90 is 5a in hexadecimal system
- alert( '\u005a' ); // Z
+ alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument)
```
Now let's see the characters with codes `65..220` (the latin alphabet and a little bit extra) by making a string of them:
@@ -515,6 +463,7 @@ for (let i = 65; i <= 220; i++) {
str += String.fromCodePoint(i);
}
alert( str );
+// Output:
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ
```
@@ -526,15 +475,15 @@ 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.
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, modern browsers support the internationalization standard [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/).
It provides a special method to compare strings in different languages, following their rules.
@@ -552,119 +501,11 @@ 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, 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
-
-```warn header="Advanced knowledge"
-The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters or other rare symbols.
-
-You can skip the section if you don't plan to support them.
-```
-
-### Surrogate pairs
-
-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".
-
-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
-```
-
-Note that surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language!
-
-We actually have a single symbol in each of the strings above, but the `length` shows a length of `2`.
-
-`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.
-
-Getting a symbol can be tricky, because surrogate pairs are treated as two characters:
-
-```js run
-alert( '𝒳'[0] ); // strange symbols...
-alert( '𝒳'[1] ); // ...pieces of the surrogate pair
-```
-
-Note that pieces of the surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage.
-
-Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard.
-
-In the case above:
-
-```js run
-// charCodeAt is not surrogate-pair aware, so it gives codes for parts
-
-alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, between 0xd800 and 0xdbff
-alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, between 0xdc00 and 0xdfff
-```
-
-You will find more ways to deal with surrogate pairs later in the chapter 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 567c9ce7a..cbd473196 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,16 +4,21 @@ 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.
+
+Compared to the debounce decorator, the behavior is completely different:
+- `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).
-
+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.**
...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.
@@ -31,8 +36,8 @@ 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 ada01f91e..c5d785493 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md
@@ -36,11 +36,11 @@ function cachingDecorator(func) {
slow = cachingDecorator(slow);
-alert( slow(1) ); // slow(1) is cached
-alert( "Again: " + slow(1) ); // the same
+alert( slow(1) ); // slow(1) is cached and the result returned
+alert( "Again: " + slow(1) ); // slow(1) result returned from cache
-alert( slow(2) ); // slow(2) is cached
-alert( "Again: " + slow(2) ); // the same as the previous line
+alert( slow(2) ); // slow(2) is cached and the result returned
+alert( "Again: " + slow(2) ); // slow(2) result returned from cache
```
In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior.
@@ -58,10 +58,9 @@ From an outside code, the wrapped `slow` function still does the same. It just g
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.
@@ -76,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(); // (*)
}
@@ -150,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:
@@ -168,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() {
@@ -212,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.
@@ -239,7 +236,7 @@ There are many solutions possible:
For many practical applications, the 3rd variant is good enough, so we'll stick to it.
-Also we need to replace `func.call(this, x)` with `func.call(this, ...arguments)`, to pass all arguments to the wrapped function call, not just the first one.
+Also we need to pass not just `x`, but all arguments in `func.call`. Let's recall that in a `function()` we can get a pseudo-array of its arguments as `arguments`, so `func.call(this, x)` should be replaced with `func.call(this, ...arguments)`.
Here's a more powerful `cachingDecorator`:
@@ -280,13 +277,15 @@ alert( worker.slow(3, 5) ); // works
alert( "Again " + worker.slow(3, 5) ); // same (cached)
```
-Now it works with any number of arguments.
+Now it works with any number of arguments (though the hash function would also need to be adjusted to allow any number of arguments. An interesting way to handle this will be covered below).
There are two changes:
- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions.
- Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function.
+## func.apply
+
Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`.
The syntax of built-in method [func.apply](mdn:js/Function/apply) is:
@@ -302,18 +301,18 @@ The only syntax difference between `call` and `apply` is that `call` expects a l
So these two calls are almost equivalent:
```js
-func.call(context, ...args); // pass an array as list with spread operator
-func.apply(context, args); // is same as using apply
+func.call(context, ...args);
+func.apply(context, args);
```
-There's only a minor difference:
+They perform the same call of `func` with given context and arguments.
-- The spread operator `...` allows to pass *iterable* `args` as the list to `call`.
-- The `apply` accepts only *array-like* `args`.
+There's only a subtle difference regarding `args`:
-So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works.
+- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`.
+- The `apply` accepts only *array-like* `args`.
-And for objects that are both iterable and array-like, like a real array, we technically could use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better.
+...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better.
Passing all arguments along with the context to another function is called *call forwarding*.
@@ -347,7 +346,7 @@ function hash(args) {
}
```
-...Unfortunately, that won't work. Because we are calling `hash(arguments)` and `arguments` object is both iterable and array-like, but not a real array.
+...Unfortunately, that won't work. Because we are calling `hash(arguments)`, and `arguments` object is both iterable and array-like, but not a real array.
So calling `join` on it would fail, as we can see below:
@@ -375,7 +374,7 @@ hash(1, 2);
The trick is called *method borrowing*.
-We take (borrow) a join method from a regular array `[].join`. And use `[].join.call` to run it in the context of `arguments`.
+We take (borrow) a join method from a regular array (`[].join`) and use `[].join.call` to run it in the context of `arguments`.
Why does it work?
@@ -393,12 +392,20 @@ Taken from the specification almost "as-is":
So, technically it takes `this` and joins `this[0]`, `this[1]` ...etc together. It's intentionally written in a way that allows any array-like `this` (not a coincidence, many methods follow this practice). That's why it also works with `this=arguments`.
+## Decorators and function properties
+
+It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them.
+
+E.g. in the example above if `slow` function had any properties on it, then `cachingDecorator(slow)` is a wrapper without them.
+
+Some decorators may provide their own properties. E.g. a decorator may count how many times a function was invoked and how much time it took, and expose this information via wrapper properties.
+
+There exists a way to create decorators that keep access to function properties, but this requires using a special `Proxy` object to wrap a function. We'll discuss it later in the article