diff --git a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md index ba13cc7be..18ba5585c 100644 --- a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -2,29 +2,31 @@ [recent browser="new"] -Оператор об’єднання з null записується як два знаки питання `??`. +Оператор об’єднання з null (англійською nullish coalescing operator) записується як два знаки питання `??`. -Оскільки `null` і `undefined` сприймаються однаково, ми введемо спеціальну домовленість. В цій статті ми будемо вважати, що значення виразу "визначене", якщо воно відрізняється від `null` та `undefined`. +```smart header="Зверніть увагу!" +Для зручності формулювання, далі в цій главі під виразом "визначене значення" ми будемо мати на увазі, що значення не є `null` і не є `undefined`. +Вираз "визначене значення" не є загальновизнаним терміном. Це придуманий нами вираз, який ми використовуємо в цій главі для зручності. +``` + +Вираз `a ?? b` поверне: -Результатом `a ?? b` буде: - `a`, якщо `a` визначене, - `b`, якщо `a` не визначене. -Інакше кажучи, `??` повертає перший аргумент, якщо він не `null/undefined`. Інакше, другий. +Інакше кажучи, `??` повертає перший аргумент, якщо він не `null`/`undefined`. Або, якщо перший аргумент є `null` або `undefined`, то повертає другий аргумент. Оператор об’єднання з null не є абсолютно новим. Це просто хороший синтаксис, щоб отримати перше "визначене" значення з двох. Ми можемо переписати вираз `result = a ?? b`, використовуючи оператори, які ми вже знаємо: ```js -result = (a !== null && a !== undefined) ? a : b; +result = a !== null && a !== undefined ? a : b; ``` -Тепер повинно бути абсолютно зрозуміло, що робить `??`. Подивімось, де це допомагає. - - -Наприклад, тут ми показуємо значення у змінній `user`, якщо її значення не `null/undefined`, інакше -- показуємо `Анонімний`: +Тепер повинно бути абсолютно зрозуміло, що робить `??`. Розгляньмо де це допомагає. +Наприклад, тут ми показуємо значення у змінній `user`, якщо її значення не `null`/`undefined`, інакше -- показуємо `Анонімний`: Ось приклад з `user`, якому не присвоєне ім’я: @@ -34,11 +36,11 @@ let user; alert(user ?? "Анонімний"); // Анонімний (user є undefined) ``` -Ми також можемо використовувати послідовність з `??`, щоб вибрати перше значення зі списку, яке не є `null/undefined`. +Ми також можемо використовувати послідовність з `??`, щоб вибрати перше значення зі списку, яке не є `null`/`undefined`. Скажімо, у нас є дані користувача в змінних `firstName`, `lastName` або `nickName`. Всі вони можуть бути не визначені, якщо користувач вирішив не вводити значення. -Ми хотіли б показати ім’я користувача, використовуючи одну з цих змінних, або показати "Анонімний", якщо всі вони `null/undefined`. +Ми хотіли б показати ім’я користувача, використовуючи одну з цих змінних, або показати `"Анонімний"`, якщо всі вони `null`/`undefined`. Використаймо оператор `??` для цього: @@ -70,19 +72,20 @@ alert(firstName || lastName || nickName || "Анонімний"); // Супер */!* ``` -Історично, оператор АБО `||` був першим. Він існує з початку JavaScript, тому розробники використовували його для цих цілей протягом тривалого часу. +Раніше був доступним лише оператор АБО `||`. Протягом тривалого часу розробники використовували для цих цілей саме його, бо він був єдиним з доступних варіантів. -З іншого боку, оператор об’єднання з null `??` було нещодавно додано в JavaScript, і причиною того було те, що люди були не дуже задоволені `||`. +Але нещодавно в JavaScript було додано оператор об’єднання з null `??`, і причиною цього було те, що люди були не до кінця задоволені `||`. Важлива різниця між ними полягає в тому, що: -- `||` повертає перше *істинне* значення. -- `??` повертає перше *визначене* значення. -Інакше кажучи, оператор `||` не розрізняє, чи значення `false`, `0`, порожній рядок `""` чи `null/undefined`. Всі вони однакові -- хибні значення. Якщо будь-яке з них є першим аргументом `||`, тоді ми отримаємо другий аргумент як результат. +- `||` повертає перше _істинне_ значення. +- `??` повертає перше _визначене_ значення. -Однак на практиці, ми хочемо використовувати типове значення лише тоді, коли змінна `null/undefined`. Тобто, коли значення дійсно невідоме/не встановлене. +Інакше кажучи, оператор `||` опрацьовує значення `false`, `0` і порожній рядок `""` так само, як `null`/`undefined`. Бо вони всі є хибними значеннями (при перетворенні на булевий тип стають `false`). Оператор `||` поверне як результат другий аргумент не лише якщо значення є `null`/`undefined`. А і якщо значення є `false`, `0` чи порожнім рядком `""`. -Наприклад, розгляньте це: +Однак на практиці дуже часто ми хочемо використовувати типове значення (другий аргумент) лише тоді, коли значення змінної є саме `null`/`undefined`. Тобто, коли значення дійсно невідоме/не встановлене. + +До прикладу: ```js run let height = 0; @@ -91,20 +94,20 @@ alert(height || 100); // 100 alert(height ?? 100); // 0 ``` -- `height || 100` перевіряє чи має змінна `height` має хибне значення, і `0` -- це дійсно хибне значення. - - отже, результатом `||` є другий аргумент, `100`. -- `height ?? 100` перевіряє змінну `height`, чи вона `null/undefined`, і це не так, - - отже, результат -- `height` "як є", тобто `0`. +- `height || 100` перетворює значення змінної `height` в булеве значення, і тоді якщо воно `false`, то повертає другий аргумент. А `0` після перетворення в булеве значення теж буде `false`. + - тут результатом `||` є другий аргумент, `100`. Навіть тоді, коли значення `0` нас влаштовує. +- `height ?? 100` перевіряє чи значення змінної `height` не є `null`/`undefined`. + - оператор `??` повертає нам значення змінної `height`, тобто `0`. -На практиці нульова висота часто є дійсним значенням, яке не слід замінювати на типове значення. Отже, `??` робить саме те, що треба. +На практиці нульова висота часто є дійсним значенням, яке не слід замінювати на типове значення. Отже, навідміну від `||`, `??` в цій ситуації робить саме те, що нам треба. ## Пріоритет Пріоритет оператора `??` такий самий, як у `||`. Він дорівнює `3` у [таблиці MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). -Це означає, що, як і `||`, оператор об’єднання з null `??` оцінюється до `=` та `?`, але після більшості інших операцій, таких як `+`, `*`. +Це означає, що, як і `||`, оператор об’єднання з null `??` виконується раніше за `=` та `?`, але після більшості інших операцій, таких як `+`, `*`. -``` js +```js let height = null; let width = null; @@ -114,14 +117,14 @@ let area = (height ?? 100) * (width ?? 50); alert(area); // 5000 ``` -В іншому випадку, якщо ми опускаємо дужки, то, оскільки `*` має вищий пріоритет, ніж `??`, то він буде виконуватися першим, що призводить до неправильних результатів. +В іншому випадку, якщо ми опускаємо дужки, то, оскільки `*` має вищий пріоритет, ніж `??`, то `*` буде виконуватися першим, що призводить до неправильних результатів. ```js // без дужок let area = height ?? 100 * width ?? 50; // ...працює так само, як попередній вираз (мабуть, це не те, що ми хочемо): -let area = height ?? (100 * width) ?? 50; +let area = height ?? 100 * width ?? 50; ``` ### Використання ?? разом з && або || @@ -131,7 +134,7 @@ let area = height ?? (100 * width) ?? 50; Код нижче викликає синтаксичну помилку: ```js run -let x = 1 && 2 ?? 3; // Синтаксична помилка +let x = 1 && 2 ?? 3; // Виконання коду зупиниться і в консолі з'явиться повідомлення про синтаксичну помилку ``` Обмеження є досить спірним, воно було додано до специфікації мови з метою уникнення помилок програмування, коли люди почнуть переходити з `||` до `??`. @@ -150,12 +153,12 @@ alert(x); // 2 - Оператор об’єднання з null `??` надає короткий спосіб вибору першого "визначеного" значення зі списку. - Він використовується для присвоєння типових значень до змінних: + Він використовується для присвоєння типових значень до змінних: - ```js - // встановлює height=100, якщо height null чи undefined - height = height ?? 100; - ``` + ```js + // встановлює height=100, якщо height null чи undefined + height = height ?? 100; + ``` -- Оператор `??` має дуже низький пріоритет -- трохи вищий, ніж `?` та `=`, тому розглядайте додавання дужок при використанні його у виразах. +- Оператор `??` має дуже низький пріоритет -- трохи вищий, ніж `?` та `=`, тому додавайте дужки при використанні його у виразах. - Цей оператор заборонено використовувати з `||` або `&&` без явних дужок. diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md index 407a47e43..48cff9fbd 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -12,18 +12,17 @@ ```js while (умова) { - // код - // так зване "тіло циклу" + // код, так зване "тіло циклу" } ``` -Доки умова є `вірною`, виконується `код` із тіла циклу. +Код з тіла циклу виконується допоки значення умови істинне (рівне `true`). -Наприклад, цикл нижче виводить `i` поки `i < 3`: +Наприклад, цикл нижче виводить `i` допоки `i < 3`: ```js run let i = 0; -while (i < 3) { // показується 0, далі 1, потім 2 +while (i < 3) { // показується 0, далі 1, потім 2. А потім `i` стає 3, і умова перестає бути істинною alert( i ); i++; } @@ -31,23 +30,23 @@ while (i < 3) { // показується 0, далі 1, потім 2 Одне виконання циклу називається *ітерацією*. Цикл в зразку вище робить три ітерації. -Якщо `i++` пропустити в коді вище, то цикл виконувався б (в теорії) вічно. На практиці, браузери надають способи зупинити такі цикли, і на серверному JavaScript(Node.js), ми можемо знищити цей процес +Якщо `i++` пропустити в коді вище, то умова б ніколи не стала б хибною, і в теорії цикл виконувався б вічно. Хоча на практиці, браузери надають способи зупинити такі цикли, і на серверному JavaScript(Node.js), ми можемо знищити цей процес. -Будь-який вираз або змінна може бути умовою циклу, а не тільки порівняння (`a < 5` чи `b !== 10`). Умова виконується і конвертується у логічне значення. +Умовою циклу може бути будь-який вираз або змінна, а не тільки порівняння (`a < 5` чи `b !== 10`). Перед первіркою браузер виконує умову і конвертує її у логічне значення. -Наприклад, коротший спосіб написання `while (i != 0)` відповідає `while (i)`: +Наприклад, коротший спосіб написання `while (i != 0)` буде `while (i)`: ```js run let i = 3; *!* -while (i) { // коли i буде 0, умова стане невірною, і цикл зупиниться +while (i) { // коли i стане 0, умова стане хибною, і цикл зупиниться */!* alert( i ); i--; } ``` -````smart header="Фігурні дужки не потрібні для однорядкової операції" +````smart header="Фігурні дужки не обов'язкові для однорядкової операції" Якщо тіло циклу має тільки одну операцію, ми можемо опустити фігурні дужки `{…}`: ```js run @@ -60,7 +59,7 @@ while (i) alert(i--); ## Цикл "do..while" -Перевірка умови може бути переміщена *нижче* тіла циклу використовуючи `do..while` синтаксис: +Перевірку умови можна перемістити *після* тіла циклу використовуючи синтаксис `do..while`: ```js do { @@ -68,7 +67,7 @@ do { } while (умова); ``` -Цикл спочатку виконує тіло, а потім перевіряє умову, і поки умова є `true`, цикл виконується знову і знову. +Тут цикл спочатку виконує тіло, а потім перевіряє умову, і поки умова є `true`, цикл виконується знову і знову. Наприклад: @@ -80,65 +79,65 @@ do { } while (i < 3); ``` -Цю форму синтаксису слід використовувати лише тоді, коли ви хочете, щоб тіло циклу виконалось **хоча б один раз**, незалежно від умови. Зазвичай, інша форма є більш бажаною `while(…) {…}` +Цю форму синтаксису слід використовувати лише тоді, коли ви хочете, щоб тіло циклу виконалось **хоча б один раз**, незалежно від умови. Але зазвичай синтаксис `while(…) {…}` є більш бажаним. ## Цикл "for" -Цикл `for` є складнішим, але також є часто використовуваним циклом. +Цикл `for` є складнішим, але його теж дуже часто використовують. Виглядає він так: ```js -for (початок; умова; крок) { +for (вступна інструкція; умова; додаткова інструкція) { // ... тіло циклу ... } ``` -Дізнаймось про значення цих трьох частин за зразком. Цикл нижче виконує `alert(i)` для `i` від `0` до `3` (але не включаючи це число `3`) +Дізнаймось про значення цих трьох частин за зразком. Цикл нижче виконує `alert(i)` допоки значення `i` менше за `3`. ```js run -for (let i = 0; i < 3; i++) { // показується 0, далі 1, потім 2 - alert(i); +for (let i = 0; i < 3; i++) { + alert(i); // показується 0, далі 1, потім 2. } ``` Розгляньмо цикл `for` по частинах: -| Назва частини | | | -|-------|----------|----------------------------------------------------------------------------| -| початок | `let i = 0` | Виконується один раз, при вході в цикл. | -| умова | `i < 3`| Перевіряється перед кожною ітерацією циклу. Якщо умова невірна, цикл зупиняєтья. | -| тіло | `alert(i)`| Виконується знову і знову, поки умова є правдивою (`true`). | -| крок| `i++` | Виконується після тіла на кожній ітерації, але перед перевіркою умови. | +| Назва частини | | | +| -------------------- | ----------- | -------------------------------------------------------------------------------------------- | +| вступна інструкція | `let i = 0` | Виконується один раз, при вході в цикл. | +| умова | `i < 3` | Перевіряється перед кожною ітерацією циклу. Якщо умова хибне (рівна false), цикл зупиняєтья. | +| тіло | `alert(i)` | Виконується знову і знову, допоки умова є істинною (`true`). | +| додаткова інструкція | `i++` | Виконується після тіла на кожній ітерації, але перед перевіркою умови. | Загальний алгоритм циклу працює так: ``` -*Початок* виконання -→ (Якщо *умова* == true → виконати тіло і виконати крок) -→ (Якщо *умова* == true → виконати тіло і виконати крок) -→ (Якщо *умова* == true → виконати тіло і виконати крок) +Виконати *вступну інструкцію* +→ (Якщо *умова* == true → виконати тіло і виконати додаткову інструкцію) +→ (Якщо *умова* == true → виконати тіло і виконати додаткову інструкцію) +→ (Якщо *умова* == true → виконати тіло і виконати додаткову інструкцію) → ... ``` -Спочатку один раз виконується `початок`, потім при кожній ітерації: перевіряється `умова`, виконується `тіло` циклу та `крок`. +Спочатку один раз виконується `вступна інструкція`, потім при кожній ітерації: перевіряється `умова`, і допоки умова істинна (рівна true), то виконується `тіло` циклу та після нього `додаткова інструкція`. Якщо ви новачок у циклах, вам може допомогти покрокове виконання цього прикладу на аркуші паперу. -Ось що відбувається в нашому випадку: +Ось що ми наказуємо зробити комп'ютеру в нашому випадку: ```js // for (let i = 0; i < 3; i++) alert(i) -// Початок виконання -let i = 0 -// Якщо умова == true → виконати тіло і виконати крок +// Виконати вступну інструкцію +let i = 0; +// Якщо умова == true → виконати тіло і виконати додаткову інстуркцію if (i < 3) { alert(i); i++ } -// Якщо умова == true → виконати тіло і виконати крок +// Якщо умова == true → виконати тіло і виконати додаткову інстуркцію if (i < 3) { alert(i); i++ } -// Якщо умова == true → виконати тіло і виконати крок +// Якщо умова == true → виконати тіло і виконати додаткову інстуркцію if (i < 3) { alert(i); i++ } -// ...кінець, тому що зараз i == 3 +// ...зупинитись. Бо зараз i == 3, отже умова перестала бути істинною ``` ````smart header="Вбудоване оголошення змінної" @@ -168,24 +167,24 @@ alert(i); // 3, змінна доступна, тому що вона оголо Будь-яку частину `for` можна пропустити. -Наприклад, ми можемо опустити `початок`, якщо нам не потрібно нічого робити перед стартом циклу. +Наприклад, ми можемо опустити `вступну інструкцію`, якщо нам не потрібно нічого робити перед стартом циклу. Ось так: ```js run let i = 0; // ми вже маємо оголошену змінну і присвоєне значення -for (; i < 3; i++) { // немає необхідності в "початку" +for (; i < 3; i++) { // немає необхідності в "вступній інструкції" alert( i ); // 0, 1, 2 } ``` -Ми також можемо видалити частину `крок`: +Ми також можемо не вказувати `додаткову інструкцію`: ```js run let i = 0; -for (; i < 3;) { +for (; i < 3; ) { alert( i++ ); } ``` @@ -200,7 +199,7 @@ for (;;) { } ``` -Зауважте, що ці двокрапки `;` повинні бути, інакше виникне синтаксична помилка. +Але зверніть увагу, що пропускати крапки з комою `;` не можна, інакше виникне синтаксична помилка. ## Переривання циклу: "break" @@ -208,7 +207,7 @@ for (;;) { Але ми можемо в будь-який момент вийти з циклу, використавши спеціальну директиву `break`. -Наприклад, наступний код запитує в користувача число доти, поки користувач їх вводить. Після того, як користувач не ввів число — цикл завершується (директивою "break") і виводить суму чисел: +Наприклад, при виконанні нижчевказаного коду комп'ютер буде запитувати в користувача числа доти, поки користувач їх вводить. Після того, як користувач не ввів число — цикл завершиться (завдяки директиві "break") і комп'ютер виведе суму чисел: ```js run let sum = 0; @@ -227,13 +226,13 @@ while (true) { alert( 'Сума: ' + sum ); ``` -Директива `break` в рядку `(*)` спрацьовує тоді, коли користувач вводить порожній рядок або скасовує введення. Ця директива негайно завершує виконання циклу і передає контроль наступному рядку за циклом, тобто на `alert`. +Директива `break` в рядку `(*)` спрацьовує тоді, коли користувач вводить порожній рядок або скасовує введення. Ця директива негайно завершує виконання циклу і перенаправляє виконання коду на наступний рядок після циклу (в нашому випадку на `alert`). Комбінація «нескінченний цикл + `break`» — чудова річ для тих ситуацій, коли умова для переривання знаходиться не на початку або кінці циклу, а всередині (або навіть в декількох місцях) тіла циклу. ## Продовження з наступної ітерації [#continue] -Директива `continue` — це "полегшена версія" `break`. Вона не зупиняє весь цикл. Натомість вона зупиняє поточну ітерацію і починає виконання циклу спочатку з наступної ітерації (якщо умова циклу досі вірна). +Директива `continue` — це "пом'якшена версія" `break`. Вона не зупиняє увесь цикл. Вона зупиняє лише поточну ітерацію і починає виконання циклу знову з наступної ітерації (якщо умова циклу досі вірна). Її зручно використовувати коли закінчили з поточною ітерацією і хочемо продовжити з наступної. @@ -242,14 +241,14 @@ alert( 'Сума: ' + sum ); ```js run no-beautify for (let i = 0; i < 10; i++) { - // якщо умова справджується, тоді пропускаємо решту тіла циклу і починаємо з наступної ітерації + // якщо умова справджується, тоді не виконуємо решту тіла циклу, а переходимо до наступної ітерації *!*if (i % 2 == 0) continue;*/!* alert(i); // 1, потім 3, 5, 7, 9 } ``` -Для парних значень змінної `i`, директива `continue` зупиняє виконання тіла циклу і передає контроль наступній ітерації в `for` (в цьому випадку це буде наступне число). Таким чином функція `alert` викликається лише для непарних значень змінної `i`. +Для парних значень змінної `i`, директива `continue` зупиняє виконання тіла циклу і передає контроль наступній ітерації в `for` (в цьому випадку це буде наступне число). Таким чином функція `alert` викликається лише коли значення `i` непарне. ````smart header="Директива `continue` допомагає зменшити рівень вкладеності" Цикл, який показує непарні значення може виглядати так: @@ -269,8 +268,8 @@ for (let i = 0; i < 10; i++) { Але побічним ефектом цього буде створення ще одного рівня вкладеності (виклик `alert` всередині фігурних дужок). Якщо код всередині `if` буде більшим за декілька рядків, то це може ускладнити загальну читабельність коду. ```` -````warn header="Директиви `break/continue` праворуч від '?' не працюють" -Майте на увазі, що такі синтаксичні конструкції, які не є виразами, не можуть використовуватися з тернарним оператором `?`. Власне, такі директиви як `break/continue` там не дозволені. +````warn header="Директиви `break`/`continue` праворуч від '?' не працюють" +Майте на увазі, що такі синтаксичні конструкції, які не є виразами, не можуть використовуватися з тернарним оператором `?`. Власне, такі директиви як `break`/`continue` там не дозволені. Наприклад, якщо взяти код: @@ -290,7 +289,7 @@ if (i > 5) { ...то такий код перестане працювати: виникне синтаксична помилка. -Це ще одна причина не використовувати для умов оператор знака питання `?`, замість повноцінного `if`. +Це ще одна причина не використовувати для умов оператор знака питання `?` замість повноцінного `if`. ```` ## Мітки для break/continue @@ -343,18 +342,18 @@ labelName: for (...) { alert('Готово!'); ``` -В коді вище, вираз `break outer` шукає зверху мітку `outer` і перериває цикл, позначений цією міткою. +В коді вище, вираз `break outer` змушує комп'ютер шукати зверху мітку `outer` і переривати цикл, позначений цією міткою. Тож виконання коду перейде з місця переривання циклу (позначене `(*)`) до функції `alert('Готово!')`. -Мітку можна перемістити в новий рядок: +Мітку можна перемістити на один рядок вище: ```js no-beautify outer: for (let i = 0; i < 3; i++) { ... } ``` -Також мітками можна використовувати з директивою `continue`. В такому разі, виконання коду перестрибне на наступну ітерацію поміченого циклу. +Також мітки можна використовувати з директивою `continue`. В такому разі, виконання коду перестрибне на наступну ітерацію поміченого циклу. ````warn header="Міткам не дозволено \"стрибати\" будь-де" Ми не можемо використовувати мітки, щоб стрибати в довільне місце в коді. @@ -377,7 +376,7 @@ label: { } ``` -...Однак, 99.9% часу `break` використовується всередині циклів, як ми бачили в прикладах вище. +...Однак, 99.9% часу `break` використовується всередині циклів, як ми бачили в прикладах вище. Виклик `continue` можливий лише всередині циклу. ````