diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index cf2f5a3c4..e1639fef4 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -36,7 +36,7 @@ 1. Рушій (вбудований, якщо це браузер) читає ("розбирає") скрипт. 2. Потім він перетворює ("компілює") скрипт у машинний код. -3. І потім цей машинний код виконується, причому досить швидко. +3. Після чого цей машинний код виконується, причому досить швидко. Рушій застосовує оптимізації на кожному етапі процесу. Він навіть слідкує за скомпільованим скриптом під час його виконання, аналізуючи дані, що проходять через нього, і оптимізує машинний код, зважаючи на ці знання. ``` @@ -106,12 +106,12 @@ JavaScript -- це єдина браузерна технологія, яка с Приклади таких мов: -- [CoffeeScript](http://coffeescript.org/) — це "синтаксичний цукор" для JavaScript. Вона вводить коротший синтаксис, дозволяючи нам писати більш чіткий і точний код. Зазвичай це до вподоби програмістам на Ruby. +- [CoffeeScript](http://coffeescript.org/) -- це "синтаксичний цукор" для JavaScript. Вона вводить коротший синтаксис, дозволяючи нам писати більш чіткий і точний код. Зазвичай це до вподоби програмістам на Ruby. - [TypeScript](http://www.typescriptlang.org/) зосереджена на додаванні "строгої типізації даних" для спрощення розробки та підтримки складних систем. Розробляється Microsoft. - [Flow](http://flow.org/) також додає типізацію даних, але іншим способом. Розробляється Facebook. - [Dart](https://dart.dev/) -- це автономна мова, яка має власний рушій, що працює в небраузерних середовищах (як-от мобільні застосунки), але також може транспілюватися в JavaScript. Розробляється Google. - [Brython](https://brython.info/) -- це транспілятор коду Python у JavaScript, що дозволяє писати застосунки на чистому Python без використання JavaScript. -- [Kotlin](https://kotlinlang.org/docs/js-overview.html) — це сучасна, лаконічна та безпечна мова програмування, яку можна компілювати для браузера або NodeJS. +- [Kotlin](https://kotlinlang.org/docs/js-overview.html) -- це сучасна, лаконічна та безпечна мова програмування, яку можна компілювати для браузера або NodeJS. Існують й інші мови. Звісно, навіть якщо ми використовуємо одну з цих транспілюючих мов, ми також повинні знати JavaScript, щоб дійсно розуміти, що робимо. diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md index 874679476..dbb772cae 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -1,7 +1,7 @@ # Довідники й специфікації -Цей сайт -- *підручник*. Він спрямований на те, щоб допомогти вам поступово вивчити мову. Проте, як тільки ви познайомитеся з основами, вам знадобляться й інші джерела. +Цей сайт -- *посібник*. Він спрямований на те, щоб допомогти вам поступово вивчити мову. Проте, як тільки ви познайомитеся з основами, вам знадобляться й інші джерела. ## Специфікація @@ -21,7 +21,7 @@ Його можна знайти за цим посиланням . -Хоча, замість пошуку на сайті, краще використовувати пошукові системи. Просто напишіть "MDN [термін]" в пошуковому запиті. Наприклад, запит "[MDN parseInt](https://www.google.com.ua/search?q=MDN+parseInt)" знайде інформацію про функцію `parseInt`. +Хоча, замість пошуку на сайті, краще використовувати пошукові системи. Просто напишіть "MDN [термін]" в пошуковому запиті. Наприклад, запит знайде інформацію про функцію `parseInt`. ## Таблиці сумісності @@ -32,6 +32,6 @@ - -- для кожної технології приведено таблицю сумісності з усіма браузерами тобто, щоб побачити, які браузери підтримують сучасні криптографічні функції, слід ввести в пошуку "[Cryptography](http://caniuse.com/#feat=cryptography)". - - таблиця з усіма можливостями мови та рушіями, які підтримують або не підтримують відповідні технології. -Всі ці ресурси корисні в повсякденній розробці, тому що вони містять корисну інформацію про деталі мови, їхню підтримку тощо. +Всі ці ресурси корисні в повсякденній розробці, бо містять корисну інформацію про деталі мови, їхню підтримку тощо. Будь ласка, збережіть собі ці сайти (або цю сторінку) вони вам знадобляться, якщо буде потреба детальніше розібратися в конкретному функціоналі мови. diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index aefd2b02a..55a31a430 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -42,3 +42,8 @@ IDE завантажує проект (який може мати багато У нашому великому світі є й інші редактори. Будь ласка, приділіть трохи часу на перегляд декількох редакторів, і виберіть той, який вам найбільш до вподоби. Вибір редактора, як і будь-якого іншого інструменту, індивідуальний, і залежить від ваших проєктів, звичок і персональних вподобань. + +Особиста думка автора: + +- Я б використовував [Visual Studio Code](https://code.visualstudio.com/), якщо розробляти доводиться переважно фронтенд. +- В іншому випадку, якщо це здебільшого інша мова/платформа та лише частково фронтенд, тоді розгляньте інші редактори, такі як XCode (Mac), Visual Studio (Windows) або сімейство Jetbrains (Webstorm, PHPStorm, RubyMine тощо, залежно від мови). diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index 3e654a638..48639de7c 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -12,7 +12,9 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` -В нас є константа `birthday`, а також `age`, яка вираховується за допомогою функції, використовуючи значення із `birthday` (в даному випадку деталі не мають значення, тому код функції не розглядається). +В нас є константа `birthday`для дати, а також константа `age`. + +Константа `age` обчислюється від `birthday` за допомогою `someCode()`, що означає виклик функції, яку ми ще не розібрали (ми скоро це зробимо!), але деталі тут не мають значення, справа в тому, що `age` обчислюється якимось чином на основі `birthday`. Чи можна використовувати великі букви для імені `birthday`? А для `age`? Чи для обох змінних? @@ -21,4 +23,3 @@ const BIRTHDAY = '18.04.1982'; // використовувати великі б const AGE = someCode(BIRTHDAY); // а тут? ``` - diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 8314cfd08..74c8fe9cf 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -64,6 +64,7 @@ let message = 'Привіт'; ``` Деякі люди також оголошують змінні в такому багаторядковому стилі: + ```js no-beautify let user = 'Іван', age = 25, @@ -103,6 +104,7 @@ let user = 'Іван' Ми можемо покласти будь-яке значення в цю коробку. Ми також можемо змінювати його стільки разів, скільки захочемо: + ```js run let message; @@ -149,11 +151,11 @@ let message = "Той"; // SyntaxError: 'message' has already been declared ```` ```smart header="Функційне програмування" -Цікаво зазначити, що [функційні](https://uk.wikipedia.org/wiki/Функційне_програмування) мови програмування, такі як [Scala](http://www.scala-lang.org/) або [Erlang](http://www.erlang.org/), забороняють змінювати значення змінних. +Цікаво зазначити, що існують [функційні](https://uk.wikipedia.org/wiki/Функційне_програмування) мови програмування, такі як [Haskell](https://uk.wikipedia.org/wiki/Haskell), в яких заборонено змінювати значення змінних. У таких мовах збережені в "коробку" значення залишаються там назавжди. Якщо нам потрібно зберегти щось інше, мова змусить нас створити нову коробку (оголосити нову змінну). Ми не можемо використати стару змінну. -Хоча на перший погляд це може здатися дивним, проте ці мови цілком підходять для серйозної розробки. Ба більше, є такі галузі, як от паралельні обчислення, де це обмеження дає певні переваги. Вивчення такої мови (навіть якщо ви не плануєте користуватися нею найближчим часом) рекомендується для розширення кругозору. +Хоча на перший погляд це може здатися дивним, проте ці мови цілком підходять для серйозної розробки. Ба більше, є такі галузі, як от паралельні обчислення, де це обмеження дає певні переваги. ``` ## Іменування змінних [#variable-naming] @@ -192,7 +194,7 @@ let my-name; // дефіс '-' недопустимий в імені ``` ```smart header="Регістр має значення" -Змінні з іменами `apple` і `AppLE` -- це дві різні змінні. +Змінні з іменами `apple` і `APPLE` -- це дві різні змінні. ``` ````smart header="Не-латинські букви дозволені, але не рекомендуються" @@ -291,6 +293,7 @@ alert(color); // #FF7F00 Назва "константа" лише означає, що змінна ніколи не зміниться. Але є константи, які відомі нам до виконання скрипта (наприклад, шістнадцяткове значення для червоного кольору), а є константи, які *вираховуються* в процесі виконання скрипта, але не змінюються після їхнього початкового присвоєння. Наприклад: + ```js const pageLoadTime = /* час, потрачений на завантаження вебсторінки */; ``` diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 8296f1a09..f43975185 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -68,9 +68,20 @@ n = 12.345; ## BigInt [#bigint-type] -У JavaScript, тип "number" не може містити числа більші за (253-1) (це `9007199254740991`), або менші за -(253-1) для від’ємних чисел. Це технічне обмеження, спричинене їхньою внутрішньою реалізацією. +У JavaScript, тип "number" не може містити числа більші за (253-1) (це `9007199254740991`), або менші за -(253-1) для від’ємних чисел. -Для більшості потреб цього достатньо, але бувають випадки, коли нам потрібні дійсно великі числа, наприклад, для криптографії або мікроксекундних часових міток (timestamps). +Якщо бути дійсно точним, тип "number" може зберігати більші цілі числа (до 1,7976931348623157 * 10308), але поза межами безпечного діапазону цілих чисел ±(2 53-1) виникне помилка точності, оскільки не всі цифри вміщуються у фіксованому 64-бітному сховищі. Тому може бути збережено "приблизне" значення. + +Наприклад, ці два числа (прямо над безпечним діапазоном) однакові: + +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 +``` + +Таким чином, усі непарні цілі числа, більші за (253-1), взагалі не можна зберігати в типі "number". + +Для більшості задач діапазону ±(253-1) цілком достатньо, але інколи нам потрібен весь діапазон дійсно великих цілих чисел, напр. для криптографії або міток часу з точністю до мікросекунд. Нещодавно в мову був доданий тип `BigInt` для представлення цілих чисел довільної довжини. @@ -263,14 +274,17 @@ typeof alert // "function" (3) У JavaScript є 8 основних типів. -- `number` для будь-яких чисел: цілих або з рухомою точкою; цілі числа обмежені до ±(253-1). -- `bigint` для цілих чисел довільної довжини. -- `string` для рядків. Рядок може мати нуль або більше символів, немає окремого типу для одного символу. -- `boolean` для `true`/`false`. -- `null` для невідомих значень — автономний тип, який має єдине значення `null`. -- `undefined` для неприсвоєних значень — автономний тип, який має єдине значення `undefined`. -- `object` для більш складних структур даних. -- `symbol` для унікальних ідентифікаторів. +- Сім примітивних типів даних: + - `number` для будь-яких чисел: цілих або з рухомою точкою; цілі числа обмежені до ±(253-1). + - `bigint` для цілих чисел довільної довжини. + - `string` для рядків. Рядок може мати нуль або більше символів, немає окремого типу для одного символу. + - `boolean` для `true`/`false`. + - `null` для невідомих значень -- автономний тип, який має єдине значення `null`. + - `undefined` для неприсвоєних значень -- автономний тип, який має єдине значення `undefined`. + - `symbol` для унікальних ідентифікаторів. + +- І один непримітивний тип даних: + - `object` для більш складних структур даних. Оператор `typeof` дає змогу нам бачити, який тип зберігається в змінній. diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md index 2f7de5783..324413407 100644 --- a/1-js/02-first-steps/07-type-conversions/article.md +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -70,7 +70,7 @@ alert(age); // NaN, помилка перетворення |`undefined`|`NaN`| |`null`|`0`| |true та false | `1` та `0` | -| `string` | Пробіли на початку та з кінця видаляються. Якщо рядок, що залишився в результаті, порожній, то результатом є `0`. В іншому випадку число "читається" з рядка. Помилка дає `NaN`. | +| `string` | Пробільні символи (пробіли, символи табуляції `\t`, символи нового рядку `\n` тощо) на початку та з кінця видаляються. Якщо рядок, що залишився в результаті, порожній, то результатом є `0`. В іншому випадку число "читається" з рядка. Помилка дає `NaN`. | Приклади: @@ -130,7 +130,7 @@ alert( Boolean(" ") ); // пробіли, також true (будь-які не |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | -| `string` | Рядок читається "як є", пробіли з обох сторін ігноруються. Пустий рядок стає `0`. Помилка дає `NaN`. | +| `string` | Рядок читається "як є", пробільні символи (пробіли, символи табуляції `\t`, символи нового рядку `\n` тощо) з обох сторін ігноруються. Пустий рядок стає `0`. Помилка дає `NaN`. | **`Перетворення на булевий тип`** -- Відбувається в логічних операціях. Може виконуватися за допомогою `Boolean(value)`. diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index 625ea2889..07fae5f2a 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -16,7 +16,7 @@ undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) ``` -1. Додавання пустого рядка `""` до `1` перетворює число `1` на рядок: `"" + 1 = "1"`; далі ми маємо `"1" + 0`, де застосовується те ж саме правило. +1. Додавання пустого рядка `"" + 1` перетворює число `1` на рядок: `"" + 1 = "1"`; далі ми маємо `"1" + 0`, де застосовується те ж саме правило. 2. Віднімання `-` (як і більшість математичних операцій) працює тільки з числами, воно перетворює порожній рядок `""` на `0`. 3. Додавання з рядком додає число `5` до рядка. 4. Віднімання завжди перетворює на числа, тому рядок `" -9 "` перетвориться на число `-9` (ігноруючи пробіли навколо нього). diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index 0d68fac1d..f24baf220 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -50,8 +50,9 @@ JavaScript підтримує такі математичні операції: Наприклад: ```js run -alert( 5 % 2 ); // 1 — остача від ділення 5 на 2 -alert( 8 % 3 ); // 2 — остача від ділення 8 на 3 +alert( 5 % 2 ); // 1 - остача від ділення 5 на 2 +alert( 8 % 3 ); // 2 - остача від ділення 8 на 3 +alert( 8 % 4 ); // 0 - остача від ділення 8 на 4 ``` ### Піднесення до степеня ** @@ -194,18 +195,18 @@ alert( +apples + +oranges ); // 5 | Пріоритет | Ім’я | Знак | |------------|------|------| | ... | ... | ... | -| 15 | унарний плюс | `+` | -| 15 | унарний мінус | `-` | -| 14 | піднесення до степеня | `**` | -| 13 | множення | `*` | -| 13 | ділення | `/` | -| 12 | додавання | `+` | -| 12 | віднімання | `-` | +| 14 | унарний плюс | `+` | +| 14 | унарний мінус | `-` | +| 13 | піднесення до степеня | `**` | +| 12 | множення | `*` | +| 12 | ділення | `/` | +| 11 | додавання | `+` | +| 11 | віднімання | `-` | | ... | ... | ... | | 2 | присвоєння | `=` | | ... | ... | ... | -Як ми бачимо, "унарний плюс" має пріоритет `15`, що вище за `12` -- пріоритет "додавання" (бінарний плюс). Саме тому, у виразі `"+apples + +oranges"`, унарні плюси виконуються перед додаванням (бінарним плюсом). +Як ми бачимо, "унарний плюс" має пріоритет `14`, що вище за `11` -- пріоритет "додавання" (бінарний плюс). Саме тому, у виразі `"+apples + +oranges"`, унарні плюси виконуються перед додаванням (бінарним плюсом). ## Присвоєння @@ -303,9 +304,9 @@ alert( n ); // 14 ```js run let n = 2; -n *= 3 + 5; +n *= 3 + 5; // right part evaluated first, same as n *= 8 -alert( n ); // 16 (права частина обчислюється першою, так само, як n *= 8) +alert( n ); // 16 ``` ## Інкремент/декремент diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index c69b56d83..2f8c85628 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -68,7 +68,7 @@ if (condition) { ## Блок "else" -Вираз `if` може містити необов’язковий блок "else" ("інакше"). Він виконується, коли умова є хибною. +Вираз `if` може містити необов’язковий блок `else` ("інакше"). Він виконується, коли умова є хибною. Наприклад: ```js run @@ -181,9 +181,9 @@ alert( message ); Спочатку може бути важко зрозуміти, що відбувається. Але, придивившись ближче, ми можемо побачити, що це звичайна послідовність перевірок: 1. Перший "знак питання" перевіряє, чи `age < 3`. -2. Якщо правда -- то повертає `'Привіт, крихітко!'`. У іншому випадку переходить до виразу після двокрапки '":"', перевіряючи `age < 18`. -3. Якщо це правда -- то повертає `'Вітаю!'`. В іншому випадку переходить до виразу після наступної двокрапки '":"', перевіряючи `age < 100`. -4. Якщо це правда -- то повертає `'Моє шанування!'`. У іншому випадку переходить до виразу після останньої двокрапки '":"', повертаючи `'Який незвичайний вік!'`. +2. Якщо правда -- то повертає `'Привіт, крихітко!'`. У іншому випадку переходить до виразу після двокрапки ":", перевіряючи `age < 18`. +3. Якщо це правда -- то повертає `'Вітаю!'`. В іншому випадку переходить до виразу після наступної двокрапки ":", перевіряючи `age < 100`. +4. Якщо це правда -- то повертає `'Моє шанування!'`. У іншому випадку переходить до виразу після останньої двокрапки ":", повертаючи `'Який незвичайний вік!'`. Ось як це буде виглядати у випадку з використанням `if..else`: 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 03f91d329..fbb61a8ee 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 @@ Відповідь: `null`, тому що це перше хибне значення зі списку. ```js run -alert( 1 && null && 2 ); +alert(1 && null && 2); ``` 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 577ca088a..b31ca3e4c 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 @@ -31,7 +31,7 @@ result = (a !== null && a !== undefined) ? a : b; ```js run let user = "Іван"; -alert(user ?? "Анонімний"); // Іван (user визначений) +alert(user ?? "Анонімний"); // Анонімний (user є undefined) ``` Ми також можемо використовувати послідовність з `??`, щоб вибрати перше значення зі списку, яке не є `null/undefined`. 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 aa386c6f9..46d7d8315 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -162,10 +162,8 @@ for (i = 0; i < 3; i++) { // використовуємо існуючу змі alert(i); // 3, змінна доступна, тому що вона оголошена поза циклом ``` - ```` - ### Пропуск частин в "for" Будь-яку частину `for` можна пропустити. @@ -286,7 +284,6 @@ if (i > 5) { ...і переробити його з використанням знака питання: - ```js no-beautify (i > 5) ? alert(i) : *!*continue*/!*; // використання continue в недозволеному місці ``` @@ -321,6 +318,7 @@ alert('Готово!'); Звичайний `break` після `input` перерве лише внутрішній цикл, а нам цього недостатньо. Ось тут нам стануть в пригоді мітки для циклів! *Мітка* складається з ідентифікатора та двокрапки перед циклом: + ```js labelName: for (...) { ... @@ -362,6 +360,7 @@ for (let i = 0; i < 3; i++) { ... } Ми не можемо використовувати мітки, щоб стрибати в довільне місце в коді. Наприклад, ось таке неможливо зробити: + ```js break label; // стрибнути в мітку label нижче (не спрацює) @@ -369,6 +368,7 @@ label: for (...) ``` Директива `break` повинна бути всередині блоку з кодом. Технічно, підійде навіть іменований блок. Наприклад: + ```js label: { // ... diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md index e7cd133f3..5c675a272 100644 --- a/1-js/02-first-steps/14-switch/article.md +++ b/1-js/02-first-steps/14-switch/article.md @@ -139,7 +139,7 @@ switch (a) { Тепер обидва варіанти `3` та `5` виводять однакове повідомлення. -Можливість групування блоків `case` - це побічний ефект того, як `switch/case` працює без `break`. Тут виконання коду `case 3` починається з рядка `(*)` та проходить через `case 5`, бо немає `break`. +Можливість групування блоків `case` -- це побічний ефект того, як `switch/case` працює без `break`. Тут виконання коду `case 3` починається з рядка `(*)` та проходить через `case 5`, бо немає `break`. ## Тип має значення diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md index b5b89428a..d4c3147ff 100644 --- a/1-js/02-first-steps/15-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -24,7 +24,7 @@ function showMessage() { ```js function name(parameter1, parameter2, ... parameterN) { - ...тіло функції... + // ...тіло функції... } ``` @@ -208,6 +208,12 @@ showMessage("Анна"); // Анна: текст не задано Тепер, якщо параметр `text` не задано, його значення стане `"текст не задано"`. +Типове значення також додається, якщо параметр існує, але суворо дорівнює `undefined`, наприклад: + +```js +showMessage("Анна", undefined); // Ann: no text given +``` + Тут `"текст не задано"` це рядок, проте це може бути складніший вираз, який обчислюється і присвоюється лише якщо параметр відсутній. Отож, такий варіант теж можливий: ```js run @@ -259,7 +265,7 @@ function showMessage(from, text) { ### Альтернативні типові параметри -Інколи виникає необхідність присвоїти типове значення для змінних під час виконання функції, а не під час її оголошення. +Інколи виникає необхідність присвоїти типові значення для змінних під час виконання функції, а не під час її оголошення. Під час виконання функції, ми можемо перевірити, чи параметр надано, порівнюючи його з `undefined`: @@ -522,7 +528,7 @@ function ім’я(параметри, розділені, комою) { Для того, щоб зробити код чистим і зрозумілим, рекомендується використовувати локальні змінні і параметри функції, не користуватися зовнішніми змінними. -Завжди легше зрозуміти функцію, яка отримує параметри, працює з ними і повертає результ. На відмінну від функції, в якої немає параметрів, але яка змінює зовнішні змінні, що може призводити до побічних ефектів. +Завжди легше зрозуміти функцію, яка отримує параметри, працює з ними і повертає результат. На відмінну від функції, в якої немає параметрів, але яка змінює зовнішні змінні, що може призводити до побічних ефектів. Найменування функцій: diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md index 64f34b9f4..ac4a389d9 100644 --- a/1-js/02-first-steps/16-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -150,7 +150,7 @@ ask("Ви згодні?", showOk, showCancel); Суть полягає в тому, що ми передаємо функцію і очікуємо, що вона буде викликана (англ. "called back") пізніше, якщо це буде потрібно. У нашому випадку, `showOk` стає колбеком, якщо відповідь — "yes", а `showCancel`, якщо відповідь — "no". -Ми можемо використати Функціональний Вираз, щоб записати ту саму функцію коротше: +Ми можемо використати Функціональний Вираз, щоб записати туж саму функцію коротше: ```js run no-beautify function ask(question, yes, no) { @@ -359,7 +359,7 @@ welcome(); // спрацює ``` -```smart header="Коли використовувати Оголошення Функції, а коли — Функціональний Вираз?" +```smart header="Коли використовувати Оголошення Функції, а коли -- Функціональний Вираз?" Зазвичай, коли нам потрібна функція, то найперше потрібно розглянути синтаксис Оголошення Функції. Він дає нам більше свободи у тому, як організовувати код, оскільки дозволяє викликати функції ще до їх визначення. Також функції `function f(…) {…}` простіше помітити в коді, ніж `let f = function(…) {…};`. Оголошення Функції легше "ловляться очима". diff --git a/1-js/02-first-steps/18-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md index df93fb120..4769e58e1 100644 --- a/1-js/02-first-steps/18-javascript-specials/article.md +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -103,14 +103,14 @@ typeof function(){} == "function" // спеціально для функцій Ми використовуємо браузер у ролі робочого середовища, тому для взаємодії з відвідувачами ми використовуємо функції: -[`prompt(question, [default])`](mdn:api/Window/prompt) -: Задає питання (`question`), а потім повертає те, що ввів відвідувач, або `null`, якщо відвідувач натиснув кнопку "Скасувати". +[`prompt(question, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) +: Задає питання `question`, а потім повертає те, що ввів відвідувач, або `null`, якщо відвідувач натиснув кнопку "Скасувати". -[`confirm(question)`](mdn:api/Window/confirm) -: Задає питання (`question`) і пропонує відвідувачу вибрати "ОК" або "Скасувати". Вибір повертається як `true/false`. +[`confirm(question)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) +: Задає питання `question` і пропонує відвідувачу вибрати ОК або Скасувати. Вибір повертається як `true/false`. -[`alert(message)`](mdn:api/Window/alert) -: Виводить повідомлення (`message`). +[`alert(message)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) +: Виводить повідомлення `message`. Всі ці функції показують *модальне вікно*; вони зупиняють виконання скрипта і не дають користувачеві взаємодіяти зі сторінкою доки не буде надана відповідь. @@ -144,7 +144,7 @@ JavaScript підтримує такі оператори: : Звичайне присвоєння: `a = b` і складені `a *= 2`. Оператори бітового зсуву -: Бітові оператори працюють з 32-бітними цілими числами на найнижчому, побітовому, рівні. Детальніше про їхнє використання можна прочитати на ресурсі [MDN](mdn:/JavaScript/Guide/Вирази_та_оператори#Бітові_оператори). +: Бітові оператори працюють з 32-бітними цілими числами на найнижчому, побітовому, рівні. Детальніше про їхнє використання можна прочитати в [документації](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators), коли це знадобиться. Умовний оператор : Єдиний оператор з трьома параметрами: `cond ? resultA : resultB`. Якщо `cond` вірно, повертається `resultA`, інакше – `resultB`. diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg index d5d2a0b93..3fe5f124f 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg index 41a3d8784..016708256 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg @@ -1 +1 @@ -213 \ No newline at end of file +213 \ No newline at end of file diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index 2eee3a113..70afe3cb5 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -143,7 +143,7 @@ function pow(x, n) { До речі, багато редакторів, наприклад [WebStorm](https://www.jetbrains.com/webstorm/) можуть їх розуміти та використовувати для автодоповнення і деякої автоматичної перевірки коду. -Також є інструменти, наприклад [JSDoc 3](https://github.com/jsdoc3/jsdoc), які можуть генерувати HTML-документацію з коментарів. Ви можете почитати більше про JSDoc тут: . +Також є інструменти, наприклад [JSDoc 3](https://github.com/jsdoc3/jsdoc), які можуть генерувати HTML-документацію з коментарів. Ви можете почитати більше про JSDoc тут: . Чому завдання було вирішено у такий спосіб? : Те, що написано є дуже важливим. Проте, те, що *не* написано може бути ще більш важливим, щоб зрозуміти, що саме відбувається. Чому завдання було вирішено саме у такий спосіб? Код не дає відповідь на це питання. diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 5f0a48d41..efe815dfc 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -69,7 +69,7 @@ describe("pow", function() { 1. Пишуть первинну специфікацію з тестами основного функціонала. 2. Створюється початкова реалізація. -3. Щоб перевірити, чи вона працює, ми використовуємо тестовий фреймворк [Mocha](http://mochajs.org/) (більш детально нижче), який виконує специфікацію. Якщо функціонал не завершено - виводяться повідомлення про помилки. Ми робимо виправлення до тих пір, поки не матимемо повністю робочий код. +3. Щоб перевірити, чи вона працює, ми використовуємо тестовий фреймворк [Mocha](https://mochajs.org/) (більш детально нижче), який виконує специфікацію. Якщо функціонал не завершено -- виводяться повідомлення про помилки. Ми робимо виправлення до тих пір, поки не матимемо повністю робочий код. 4. Тепер ми маємо початкову реалізацію з тестами. 5. Ми додаємо більше способів використання до специфікації, навіть таких, що поки що не підтримуються реалізацією. Виконання тестів знову завершиться невдачою. 6. Переходимо на 3-й пункт, змінюємо реалізацію, щоб вона відповідала тестам і вони не повертали повідомлення про помилку. @@ -79,15 +79,15 @@ describe("pow", function() { Давайте розглянемо цей процес розробки на нашому прикладі. -Перший пункт вже виконано - ми маємо первинну специфікацію для функції `pow`. Теперр, перед початком реалізації, давайте використаємо декілька бібліотек JavaScript для виконання тестів, щоб перевірити, що вони працюютть (вони всі завершаться невдачою). +Перший пункт вже виконано -- ми маємо первинну специфікацію для функції `pow`. Тепер, перед початком написання коду, давайте використаємо декілька бібліотек JavaScript для запуску тестів, щоб перевірити, що вони працюють (звичайно, без коду функції, вони всі завершаться невдачою). ## Специфікація в дії Тут у посібнику ми будемо використовувати такі бібліотеки JavaScript для тестів: -- [Mocha](http://mochajs.org/) -- базовий фреймворк: він забезпечує нас загальними функціями для тестування, в тому числі `describe` та `it`, а також головною функцією, що виконує тести. -- [Chai](http://chaijs.com) -- бібліотека з багатьма припущеннями. Вона дозволяє використовувати безліч різних припущень, але поки що нам потрібне лише припущення `assert.equal`. -- [Sinon](http://sinonjs.org/) -- бібліотека для "шпигування" за функціями, емуляції вбудованих функцій тощо, нам це знадобиться набагато пізніше. +- [Mocha](https://mochajs.org/) -- базовий фреймворк: він забезпечує нас загальними функціями для тестування, в тому числі `describe` та `it`, а також головною функцією, що виконує тести. +- [Chai](https://chaijs.com) -- бібліотека для порівняння і оцінки роботи коду. Вона дозволяє використовувати безліч різних порівнянь, але поки що нам потрібне лише функція порівняння `assert.equal`. +- [Sinon](https://sinonjs.org/) -- бібліотека для "шпигування" за функціями, емуляції вбудованих функцій тощо, нам це знадобиться набагато пізніше. Ці бібліотеки підходять як для тестування в браузері, так і на стороні сервера. Тут ми розглянемо варіант тестування в браузері. @@ -338,14 +338,14 @@ describe("pow", function() { ```smart header="Інші припущення" Зверніть увагу на припущення `assert.isNaN`: воно перевіряє на `NaN`. -Є також інші припущення у [Chai](http://chaijs.com), наприклад: +Є також інші функції порівняння у [Chai](https://chaijs.com), наприклад: -- `assert.equal(value1, value2)` -- перевіряє рівність `value1 == value2`. +- `assert.equal(value1, value2)` -- перевіряє рівність `value1 == value2`. - `assert.strictEqual(value1, value2)` -- перевіряє сувору рівність `value1 === value2`. - `assert.notEqual`, `assert.notStrictEqual` -- зворотня перевірка до вищевказаної. - `assert.isTrue(value)` -- перевіряє, що `value === true` - `assert.isFalse(value)` -- перевіряє, що `value === false` -- ...повний список знаходиться в [документації](http://chaijs.com/api/assert/) +- ...повний список знаходиться в [документації](https://chaijs.com/api/assert/) ``` Таким чином, ми повинні додати пару рядків до функції `pow`: diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index e5afe3c95..f92aa15af 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,7 +1,7 @@ # Поліфіли і транспілятори -Мова JavaScript постійно розвивається. До неї регулярно додаються нові пропозиції, далі вони аналізуються і, якщо вважаються гідними, додаються до списку , а потім переходять до [специфікації](http://www.ecma-international.org/publications/standards/Ecma-262.htm). +Мова JavaScript постійно розвивається. До неї регулярно додаються нові пропозиції, далі вони аналізуються і, якщо вважаються гідними для розширення мови, додаються до списку , а потім переходять до [специфікації](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). Команди, які розробляють рушії JavaScript, мають власні уявлення про те, що потрібно реалізувати спочатку. Вони можуть вирішити реалізувати пропозиції, що знаходяться у чернетці, і відкласти речі, які вже є у специфікації, оскільки вони менш цікаві або їх важче розробити. @@ -42,7 +42,7 @@ height = (height !== undefined && height !== null) ? height : 100; Говорячи про імена, [Babel](https://babeljs.io) -- один з найвідоміших транспіляторів. -Сучасні збирачі проєктів, такі як [webpack](http://webpack.github.io/), надають засоби для автоматичного запуску транспілятора при кожній зміні коду, тому його дуже легко інтегрувати в процес розробки. +Сучасні збирачі проєктів, такі як [webpack](https://webpack.js.org/), надають засоби для автоматичного запуску транспілятора при кожній зміні коду, тому його дуже легко інтегрувати в процес розробки. ## Поліфіли @@ -73,7 +73,7 @@ JavaScript дуже динамічна мова -- скрипти можуть Є два цікавих поліфіла: - [core js](https://github.com/zloirock/core-js), що підтримує багато функціоналу, дозволяє включати лише необхідні функції. -- [polyfill.io](http://polyfill.io) - сервіс, що генерує скрипт з поліфілами залежно від потрібних нам функцій та браузера користувача. +- [polyfill.io](https://polyfill.io) -- сервіс, що генерує скрипт з поліфілами відповідно до потрібних нам функцій та браузера користувача. ## Підсумки @@ -82,7 +82,7 @@ JavaScript дуже динамічна мова -- скрипти можуть Просто не забувайте використовувати транспілятор (якщо будете використовувати сучасний синтаксис чи оператори) та поліфіли (щоб додавати функції, які можуть бути відсутні). Це дозволить впевнитися, що код працює на різних рушіях. -Наприклад, пізніше (коли достатньо вивчите JavaScript), ви зможете налаштувати систему складання проєкту на основі [webpack](https://webpack.js.org/) із плаґіном [babel-loader](https://github.com/babel/babel-loader). +Наприклад, пізніше (коли достатньо вивчите JavaScript), ви зможете налаштувати систему збору проєкту на основі [webpack](https://webpack.js.org/) із плаґіном [babel-loader](https://github.com/babel/babel-loader). Ось хороші ресурси, де можна дізнатися поточний стан підтримки різного функціоналу: - - для чистого JavaScript. diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 0bb631fa3..1df967a6c 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -355,7 +355,7 @@ alert( "test" in obj ); // true, властивість існує! Такі ситуації трапляються дуже рідко, тому що `undefined` не слід чітко призначати. Ми в основному використовуємо `null` для "невідомих" або "порожніх" значень. Отже, оператор `in` -- екзотичний гість у коді. -## Цикл "for..in" +## Цикл "for..in" [#forin] Для перебору всіх властивостей об’єкта використовується цикл `for..in`. Цей цикл відрізняється від вивченого раніше циклу `for(;;)`. diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md index 804e77b5f..e5192f1f0 100644 --- a/1-js/04-object-basics/02-object-copy/article.md +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -100,13 +100,35 @@ alert( a == b ); // false Для порівнянь, таких як `obj1 > obj2`, або для порівняння з примітивом `obj == 5`, об’єкти перетворюються на примітиви. Незабаром ми вивчимо, як працюють перетворення об’єктів, але правду кажучи, такі порівняння потрібні вкрай рідко — зазвичай вони з’являються в результаті неправильного програмування. +````smart header="Об’єкти у const-змінних можна змінювати" +Важливим побічним ефектом зберігання об’єктів як посилань є те, що об’єкт, оголошений як `const`, *може* бути змінений. + +Наприклад: + +```js run +const user = { + name: "Іван" +}; + +*!* +user.name = "Петро"; // (*) +*/!* + +alert(user.name); // Петро +``` + +Може здатися, що рядок `(*)` призведе до помилки, але це не так. Значення у змінній `user` є постійним, воно завжди має посилатися на той самий об’єкт, але властивості цього об’єкта можуть змінюватися. + +Інакше кажучи, `const user` видає помилку, лише якщо ми намагаємося присвоїти у саму змінну інше значення: `user = ...`. + +Тим не менш, якщо нам дійсно потрібно створювати незмінні властивості об’єкта, це також можливо, але з використанням зовсім інших методів. Про це ми згадаємо у розділі . +```` + ## Клонування та злиття об’єктів, Object.assign [#cloning-and-merging-object-assign] Отже, копіювання змінної об’єкта створює ще одне посилання на той самий об’єкт. -Але що, якщо нам потрібно дублювати об’єкт? Створити незалежну копію, клон? - -Це теж можливо, але трохи складніше, оскільки в JavaScript немає вбудованого методу для цього. Насправді, це потрібно досить рідко. У більшості випадків нам достатньо копіювання за посиланням. +Але що, якщо нам потрібно створити копію, клон об'єкта? Але якщо ми насправді цього хочемо, то нам потрібно створити новий об’єкт і відтворити структуру існуючого, перебираючи та копіюючи його властивості. @@ -114,88 +136,93 @@ alert( a == b ); // false ```js run let user = { - name: "Іван", + name: "Микола", age: 30 }; *!* -let clone = {}; // новий порожній об’єкт +let clone = {}; // новий порожній об'єкт -// скопіюймо в нього всі властивості з user +// давайте скопіюємо в нього всі властивості з user for (let key in user) { clone[key] = user[key]; } */!* -// тепер клон - це повністю незалежний об’єкт з однаковим вмістом -clone.name = "Петро"; // змінемо його вміст +// тепер clone є повністю незалежним об'єктом з тим самим вмістом +clone.name = "Петро"; // змінив дані в ньому -alert( user.name ); // як і раніше Іван залишився в оригінальному об’єкті +alert( user.name ); // значення "Микола" все ще в оригінальному об'єкті ``` -Також ми можемо використати метод [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) для цього. +Ми також можемо скористатися методом [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). -Його синтаксис: +Синтаксис: ```js -Object.assign(dest, [src1, src2, src3...]) +Object.assign(dest, ...sources) ``` +- Перший аргумент `dest` є цільовим об'єктом. +- Подальші аргументи -- це список вихідних об'єктів. -- Перший аргумент `dest` -- це цільовий об’єкт, у який ми будемо копіювати. -- Наступні аргументи `src1, ..., srcN` (їх може бути скільки завгодно) - це вихідні об’єкти, з яких ми будемо копіювати. -- Він копіює властивості всіх вихідних об’єктів `src1, ..., srcN` у цільовий` dest`. Іншими словами, властивості всіх аргументів, починаючи з другого, копіюються в перший об’єкт. -- Виклик повертає `dest`. +Він копіює властивості всіх вихідних об’єктів у цільовий `dest`, а потім повертає його як результат. -Наприклад, ми можемо використовувати його для об’єднання кількох об’єктів в один: -```js -let user = { name: "Іван" }; +Наприклад, у нас є об’єкт `user`, давайте додамо до нього пару властивостей з дозволами: + +```js run +let user = { name: "Микола" }; let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; *!* -// копіює всі властивості з permissions1 та permissions2 у user +// копіює всі властивості з permissions1 та permissions2 в user Object.assign(user, permissions1, permissions2); */!* -// тепер user = { name: "Іван", canView: true, canEdit: true } +// Тепер user = { name: "Микола", canView: true, canEdit: true } +alert(user.name); // Микола +alert(user.canView); // true +alert(user.canEdit); // true ``` -Якщо приймаючий об’єкт (`user`) вже має властивість з таким ім’ям, її буде перезаписано: +Якщо скопійована назва властивості вже існує, вона буде перезаписана: ```js run -let user = { name: "Іван" }; +let user = { name: "Микола" }; Object.assign(user, { name: "Петро" }); -alert(user.name); // тепер user = { name: "Петро" } +alert(user.name); // now user = { name: "Петро" } ``` -Ми також можемо використовувати `Object.assign` щоб замінити цикл `for..in` для простого клонування: +Ми також можемо використовувати `Object.assign` щоб виконати просте клонування об’єкта: -```js +```js run let user = { - name: "Іван", + name: "Микола", age: 30 }; *!* let clone = Object.assign({}, user); */!* -``` -Він копіює всі властивості `user` у порожній об’єкт і повертає його. +alert(clone.name); // Микола +alert(clone.age); // 30 +``` +Він копіює всі властивості `user` в порожній об’єкт і повертає його. -Існують також інші методи клонування об’єкта, напр. за допомогою [оператора розширення](info:rest-parameters-spread) `clone = {...user}`, це висвітлено пізніше у підручнику. +Існують також інші методи клонування об’єкта, напр. [spread syntax](info:rest-parameters-spread) `clone = {...user}`, розглянуті далі в посібнику. ## Вкладене клонування -До цього часу ми вважали, що всі властивості `user` є примітивами. Але властивості можуть бути посиланнями на інші об’єкти. Що з ними робити? +Досі ми припускали, що всі властивості `user` є примітивами. Але властивості можуть бути посиланнями на інші об’єкти. -Наприклад: +Ось так: ```js run let user = { - name: "Іван", + name: "Микола", sizes: { height: 182, width: 50 @@ -205,13 +232,11 @@ let user = { alert( user.sizes.height ); // 182 ``` -Тепер при клонуванні недостатньо просто скопіювати `clone.sizes = user.sizes`, тому що `user.sizes` є об’єктом, і він буде скопійований за посиланням. Тому `clone` і `user` у своїх властивостях `sizes` будуть посилатися на той самий об’єкт: - -Ось так: +Тепер недостатньо скопіювати `clone.sizes = user.sizes`, тому що `user.sizes` є об’єктом і буде скопійовано за посиланням, тому `clone` і `user` матимуть однакові `sizes`: ```js run let user = { - name: "Іван", + name: "Микола", sizes: { height: 182, width: 50 @@ -220,40 +245,73 @@ let user = { let clone = Object.assign({}, user); -alert( user.sizes === clone.sizes ); // true, той самий об’єкт +alert( user.sizes === clone.sizes ); // true, той же об'єкт -// user і clone мають посилання на єдиний об’єкт у властивості sizes -user.sizes.width++; // міняємо властивість з одного місця -alert(clone.sizes.width); // 51, бачимо результат в іншому об’єкті +// user та clone поділяють sizes +user.sizes.width = 60; // змінюємо властивість в одному місці +alert(clone.sizes.width); // 60, отримуємо результат в іншому ``` -Щоб це виправити, слід використовувати цикл клонування, який перевіряє кожне значення `user[key]`, і якщо це об’єкт, то також копіює його структуру. Це називається "глибоким клонуванням". +Щоб виправити це та зробити `user` і `clone` справді окремими об'єктами, ми повинні використати цикл клонування, який перевіряє кожне значення `user[key]` і, якщо це об'єкт, то також повторює його структуру. Це називається "глибоке клонування(deep cloning)" або "структурне клонування(structured cloning)". Існує метод [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone), який реалізує таке глибоке клонування. -Для його реалізації ми можемо використати рекурсію. Або, щоб не винаходити колесо, взяти існуючу реалізацію, наприклад [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) з бібліотеки JavaScript [lodash](https://lodash.com). +### structuredClone -````smart header="об’єкти у const-змінних можна змінювати" -Важливим побічним ефектом зберігання об’єктів як посилань є те, що об’єкт, оголошений як `const`, *може* бути змінений. +Виклик `structuredClone(object)` клонує `object` з усіма вкладеними властивостями. -Наприклад: +Ось як ми можемо використовувати це в нашому прикладі: ```js run -const user = { - name: "Іван" +let user = { + name: "Микола", + sizes: { + height: 182, + width: 50 + } }; *!* -user.name = "Петро"; // (*) +let clone = structuredClone(user); */!* -alert(user.name); // Петро +alert( user.sizes === clone.sizes ); // false, різні об'єкти + +// user та clone тепер абсолютно не пов'язані між собою +user.sizes.width = 60; // тепер змінюємо властивість в одному місці +alert(clone.sizes.width); // 50, інше місце не пов'язане з попереднім ``` -Може здатися, що рядок `(*)` призведе до помилки, але це не так. Значення у змінній `user` є постійним, воно завжди має посилатися на той самий об’єкт, але властивості цього об’єкта можуть змінюватися. +Метод `structuredClone` може клонувати більшість типів даних, таких як об’єкти, масиви, примітивні значення. -Інакше кажучи, `const user` видає помилку, лише якщо ми намагаємося присвоїти у саму змінну інше значення: `user = ...`. +Він також підтримує циклічні посилання, коли властивість об’єкта посилається на сам об’єкт (безпосередньо або через ланцюжок чи посилання). -Тим не менш, якщо нам дійсно потрібно створювати незмінні властивості об’єкта, це також можливо, але з використанням зовсім інших методів. Про це ми згадаємо у розділі . -```` +Наприклад: + +```js run +let user = {}; +// давайте створимо циклічне посилання: +// user.me посилається на user +user.me = user; + +let clone = structuredClone(user); +alert(clone.me === clone); // true +``` + +Як бачите, `clone.me` посилається на `clone`, а не на `user`! Отже, циклічне посилання також було клоновано правильно. + +Хоча бувають випадки, коли `structuredClone` не працює. + +Наприклад, коли об’єкт має властивість з функцією: + +```js run +// помилка +structuredClone({ + f: function() {} +}); +``` + +Властивості з функціями не підтримуються. + +Для обробки таких складних випадків нам може знадобитися використовувати комбінацію методів клонування, написати спеціальний код або, щоб не винаходити колесо, взяти існуючу реалізацію, наприклад [_.cloneDeep(obj)](https://lodash.com /docs#cloneDeep) із бібліотеки JavaScript [lodash](https://lodash.com). ## Підсумки diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md index 921447187..a35dbcd06 100644 --- a/1-js/04-object-basics/03-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -183,12 +183,12 @@ family = null; Ми також можемо уявити собі цей процес, як виливання великого відра фарби починаючи з коренів, фарба протікає через усі посилання і позначає всі об’єкти, до яких можна дістатися. Потім непозначені об’єкти видаляються. -Це концепція того, як працює збирання сміття. Рушій JavaScript застосовує багато оптимізацій, щоб прискорити це та не вплинути на виконання. +Це концепція того, як працює збирання сміття. Рушій JavaScript застосовує багато оптимізацій, щоб прискорити це та не додати затримок при виконанні коду. -Some of the optimizations: +Деякі з оптимізацій: -- **Збірка поколінь (Generational collection)** -- об’єкти поділяються на два набори: "нові" та "старі". Багато об’єктів з’являється, виконує свою роботу і швидко гине, їх можна агресивно прибирати. Ті, що виживають досить довго, стають "старими" і оглядаються рідше. -- **Інкрементний збір (Incremental collection)** -- якщо об’єктів багато, і ми намагаємось пройтися і позначити весь набір об’єктів одночасно, це може зайняти деякий час і ввести видимі затримки у виконанні. Тому рушій намагається розділити збирання сміття на частини. Потім частини виконуються по одній, окремо. Це вимагає додаткового обліку між ними для відстеження змін, але ми маємо багато маленьких затримок замість великих. +- **Збірка поколінь (Generational collection)** -- об’єкти поділяються на два набори: "нові" та "старі". Багато об'єктів мають короткий термін служби, вони з’являються, виконують свою роботу і швидко стають непотрібними. Тому має сенс відстежувати нові об’єкти та очищати від них пам’ять, якщо це так. Ті, що використовуються досить довго, стають "старими" і оглядаються рідше. +- **Інкрементний збір (Incremental collection)** -- якщо об’єктів багато, і ми намагаємось пройтися і позначити весь набір об’єктів одночасно, це може зайняти деякий час і ввести видимі затримки у виконанні. Тому рушій намагається розділити збирання сміття на частини. Потім частини виконуються по одній, окремо. Таким чином відбувається багато дрібних зборів сміття замість одного великого. Це вимагає додаткового обліку між ними для відстеження змін, але ми маємо багато маленьких затримок замість однієї великої. - **Збір під час простою (Idle-time collection)** -- завзвичай збирання сміття працює лише під час простою процесора, щоб зменшити можливий вплив на виконання. Існують й інші оптимізації та варіанти алгоритмів збирання сміття. Але як би нам не хотілося описати їх тут, ми повинні утриматися від цього, тому що різні інтерпретатори JavaScript застосовують різні прийоми і хитрощі. І, що ще важливіше, все змінюється в міру розвитку інтерпретаторів, тому глибше вивчення "заздалегідь" без реальної потреби, ймовірно, не варто того. Якщо, звичайно, це не питання чистого інтересу, нижче для вас будуть деякі посилання. @@ -199,7 +199,7 @@ Some of the optimizations: - Збирання сміття здійснюється автоматично. Ми не можемо примусити або запобігти цьому. - Об’єкти зберігаються в пам’яті, поки вони досяжні. -- Посилання -- це не те ж саме, що бути досяжним (з кореня): декілька взаємопов’язаних об’єктів можуть стати недосяжними усі разом. +- Посилання -- це не те ж саме, що бути досяжним (з кореня): декілька взаємопов’язаних об’єктів можуть стати недосяжними усі разом. Ми розібрали це в прикладі вище. Сучасні рушії реалізують передові алгоритми збирання сміття. @@ -207,6 +207,6 @@ Some of the optimizations: Якщо ви знайомі з низькорівневим програмуванням, більш детальна інформація про збирання сміття у рушії V8 міститься у статті [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). -[V8 blog](https://v8.dev/) також час від часу публікує статті про зміни в управлінні пам’яттю. Зрозуміло, вам необхідно розуміти, як влаштований всередині рущія V8 в цілому. Про це ви можете прочитати у блозі [Вячеслава Єгорова](http://mrale.ph) який працював одним з інженерів V8. Я кажу: "V8", тому що він найкраще висвітлений статтями в Інтернеті. Для інших інтерпретаторів деякі підходи схожі, але збирання сміття відрізняється в багатьох аспектах. +[V8 blog](https://v8.dev/) також час від часу публікує статті про зміни в управлінні пам’яттю. Зрозуміло, вам необхідно розуміти, як влаштований всередині рущія V8 в цілому. Про це ви можете прочитати у блозі [Вячеслава Єгорова](https://mrale.ph) який працював одним з інженерів V8. Я кажу: "V8", тому що він найкраще висвітлений статтями в Інтернеті. Для інших інтерпретаторів деякі підходи схожі, але збирання сміття відрізняється в багатьох аспектах. Глибоке розуміння роботи інтерпретаторів необхідно, коли вам потрібні низькорівневі оптимізації. Було б розумно планувати їх вивчення тільки як наступний крок після вивчення мови JavaScript. diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md index c1575b6eb..0d752ea64 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md @@ -10,8 +10,8 @@ importance: 2 function A() { ... } function B() { ... } -let a = new A; -let b = new B; +let a = new A(); +let b = new B(); alert( a == b ); // true ``` diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md index 1d2892db0..c95e5791b 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md @@ -6,7 +6,7 @@ importance: 5 Створіть функцію-конструктор `Calculator`, який створює об’єкти з трьома методами: -- `read()` запитує два значення за допомогою `prompt` і запам'ятовує їх у властивостях об’єкта. +- `read()` запитує два значення за допомогою `prompt` і записує їх у властивості об’єкта з іменами `a` та `b`. - `sum()` повертає суму цих властивостей. - `mul()` повертає результат множення даних властивостей. diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md index 896b1459a..ed8e248e0 100644 --- a/1-js/04-object-basics/08-symbol/article.md +++ b/1-js/04-object-basics/08-symbol/article.md @@ -29,7 +29,7 @@ let id = Symbol(); let id = Symbol("id"); ``` -Символи гарантовано будуть унікальними. Навіть якщо ми створюємо багато символів з однаковим описом, вони мають різні значення. Опис - це просто мітка, яка ні на що не впливає. +Символи гарантовано будуть унікальними. Навіть якщо ми створюємо багато символів з однаковим описом, вони мають різні значення. Опис -- це просто мітка, яка ні на що не впливає. Наприклад, ось два символи з однаковим описом -- вони не рівні: @@ -44,7 +44,7 @@ alert(id1 == id2); // false Якщо ви знайомі з Ruby чи іншою мовою програмування, яка також має «символи» - будь ласка, не думайте що це те саме. Символи в JavaScript мають свої особливості. -Отже, підводячи підсумок, символи -- це "примітивні унікальні значення" з додатковим описом. Давайте подивимося, де ми можемо їх використовувати. +Отже, підсумовуючи, символи -- це "примітивні унікальні значення" з додатковим описом. Давайте подивимося, де ми можемо їх використовувати. ````warn header="Символи не перетворюються автоматично в рядок" Більшість значень у JavaScript підтримують неявне перетворення в рядок. Наприклад, ми можемо помістити майже будь-яке значення в `alert`, і воно автоматично перетворить його в рядок. Символи -- вони особливі. Вони не перетворюються автоматично. @@ -103,9 +103,9 @@ alert( user[id] ); // ми можемо отримати доступ до да Яка користь від використання `Symbol("id")` замість рядка `"id"`? -Оскільки об’єкт `user` належить сторонньому коду, і цей код також працює з ними, нам не варто просто додавати до нього будь-які поля. Це небезпечно. Але до символу неможливо отримати доступ випадково, сторонній код, ймовірно, навіть не побачить його, тому, додавання поля до об’єкта не викличе ніяких проблем. +Оскільки об’єкт `user` належить сторонньому коду, і цей код також працює з ними, нам не варто просто додавати до нього будь-які поля. Це небезпечно. Але до символу неможливо отримати доступ випадково, сторонній код, ймовірно, навіть не побачить його, тому, додавання поля до об’єкта `user` не викличе жодних проблем. -Крім того, уявіть, що інший скрипт хоче мати власний ідентифікатор всередині `user` для своїх цілей. Це може бути ще одна бібліотека JavaScript, так що сценарії абсолютно не знають один про одного. +Крім того, уявіть, що інший скрипт хоче мати власний ідентифікатор всередині `user` для своїх цілей. Тоді цей скрипт може створити свій власний `Symbol("id")`, як ось цей: @@ -169,7 +169,7 @@ for (let key in user) alert(key); // name, age (дані ключі не є си */!* // але працює прямий доступ за допомогою символу -alert( "Прямий доступ: " + user[id] ); +alert( "Прямий доступ: " + user[id] ); // Прямий доступ: 123 ``` [Object.keys(user)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) також ігнорує їх. Це частина загального принципу "приховування символьних властивостей". Якщо інший скрипт або бібліотека спробує перебрати наш об’єкт за допомогою цикла, він несподівано не отримає доступ до символьних властивостей. @@ -284,6 +284,6 @@ alert( localSymbol.description ); // name Тож ми можемо ховати щось потрібне для нас в об’єкти, що сторонні не повинні бачити, використовуючи символічні властивості. -2. JavaScript використовує багато системних символів, доступних як `Symbol.*`. Ми можемо використовувати їх для зміни деяких вбудованих форм поведінки. До прикладу, пізніше в підручнику ми будемо використовувати `Symbol.iterator` для [ітераторів](info:iterable), `Symbol.toPrimitive` для налаштування [перетворення об’єкта в примітив](info:object-toprimitive) та інше. +2. JavaScript використовує багато системних символів, доступних як `Symbol.*`. Ми можемо використовувати їх для зміни деяких вбудованих форм поведінки. До прикладу, пізніше в посібнику ми будемо використовувати `Symbol.iterator` для [ітераторів](info:iterable), `Symbol.toPrimitive` для налаштування [перетворення об’єкта в примітив](info:object-toprimitive) тощо. -Технічні символи не приховані на 100%. Існує вбудований метод [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) що дозволяє отримати всі символи. Також існує метод з іменем [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) що повертає *всі* ключі об’єкта, включаючи символьні. Тож вони насправді не зовсім приховані. Але більшість бібліотек, вбудованих функцій та конструкцій синтаксису не використовують ці методи. +Технічно символи не приховані на 100%. Існує вбудований метод [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) що дозволяє отримати всі символи. Також існує метод з іменем [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) що повертає *всі* ключі об’єкта, включаючи символьні. Тож вони насправді не зовсім приховані. Але більшість бібліотек, вбудованих функцій та конструкцій синтаксису не використовують ці методи. diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index 3568513f3..255fa659f 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -136,8 +136,8 @@ alert(user + 500); // hint: default -> 1500 Якщо немає `Symbol.toPrimitive` тоді JavaScript намагається знайти методи `toString` і `valueOf`: -- Для `"string"` підказка: виклик методу `toString`, і якщо цей метод не існує, то `valueOf` (таким чином `toString` має пріоритет при перетворенні в рядок). -- Для інших підказок: `valueOf`, і якщо це не існує, то `toString` (таким чином `valueOf` має пріоритет для математики). +- Для `"string"` підказка: виклик методу `toString`, і якщо цей метод не існує або якщо він повертає об'єкт замість примітивного значення, то викликати `valueOf` (таким чином `toString` має пріоритет при перетворенні в рядок). +- Для інших підказок: `valueOf`, і якщо це не існує або якщо він повертає об'єкт замість примітивного значення, то `toString` (таким чином `valueOf` має пріоритет для математики). Методи `toString` і `valueOf` походять з давніх часів. Вони не є символами (багато часу назад символи не існували), а скоріше є "звичними" методами, що названі за допомогою рядків. Вони забезпечують альтернативний шлях "старого стилю" для реалізації перетворення. @@ -225,7 +225,7 @@ alert(user + 500); // toString -> Іван500 Як ми вже знаємо, багато операторів та функцій виконують перетворення типу, наприклад, множення `*` перетворює операнди в цифри. Якщо ми передамо об’єкт як аргумент, то є два етапи: -1. об’єкт перетворюється на примітив (використовуючи правила, описані вище). +1. Об’єкт перетворюється на примітив (використовуючи правила, описані вище). 2. Якщо отриманий примітив не є правильним типом, він перетворюється. Наприклад: diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md index a6f368b0a..2faf4acb8 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md @@ -15,5 +15,4 @@ str.test = 5; alert(str.test); ``` - Як ви думаєте, чи буде це працювати? Що буде показано? diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index a4c414a16..19281c3c7 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -104,10 +104,10 @@ if (zero) { // zero є true, тому що це об’єкт } ``` - -З іншого боку, використання тих же самих функцій `String / Number / Boolean` без `new` є абсолютно розумною і корисною річчю. Вони перетворюють значення у відповідний тип: до рядка, числа або булевого (примітиву). +З іншого боку, використання тих же самих функцій `String/Number/Boolean` без `new` є абсолютно розумною і корисною річчю. Вони перетворюють значення у відповідний тип: рядок, число або булеве значення (примітиву). Наприклад, це цілком правильно: + ```js let num = Number("123"); // конвертує рядок в число ``` diff --git a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md index c6c22ed31..f9bbc4248 100644 --- a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md +++ b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md @@ -28,5 +28,5 @@ alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000 ```js run -alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(округлене) -> 6.4 +alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(округлене) -> 6.4 ``` diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index ff01ee036..ed7f9a8be 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -2,9 +2,9 @@ У сучасному JavaScript існує два типи чисел: -1. Звичайні числа в JavaScript, що зберігаються у 64-бітному форматі [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), також відомі як "подвійні точні числа з плаваючою комою". Це числа, які ми використовуємо більшість часу, і про них ми поговоримо в цьому розділі. +1. Звичайні числа в JavaScript, що зберігаються у 64-бітному форматі [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), також відомі як "подвійні точні числа з плаваючою комою". Це числа, які ми використовуємо більшість часу, і про них ми поговоримо в цьому розділі. -2. Числа BigInt, для відображення цілих чисел довільної довжини. Іноді вони потрібні, оскільки звичайне число не може безпечно перевищувати 253 або бути менше -253. Оскільки числа BigInt використовуються в декількох спеціальних областях, їм присвячено окремий розділ . +2. Числа BigInt, для відображення цілих чисел довільної довжини. Іноді вони потрібні, оскільки звичайне число не може безпечно перевищувати (253-1) або бути менше ніж -(253-1), як ми згадували раніше в розділі . Оскільки числа BigInt використовуються в декількох спеціальних областях, їм присвячено окремий розділ . То ж тут ми поговоримо про звичайні числа. Поглибимо наші знання про них. @@ -63,6 +63,9 @@ let mcs = 1e-6; // шість нулів зліва від 1 // -6 ділиться на 1 з 6 нулями 1.23e-6 === 1.23 / 1000000; // 0.00000123 + +// an example with a bigger number +1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times ``` ### Двійкові, вісімкові та шістнадцяткові числа @@ -178,7 +181,7 @@ alert( num.toString(2) ); // 11111111 alert( num.toFixed(1) ); // "12.4" ``` - Зверніть увагу, що результат `toFixed` - це рядок. Якщо десяткова частина коротша, ніж потрібно, нулі додаються до кінця: + Зверніть увагу, що результат `toFixed` -- це рядок. Якщо десяткова частина коротша, ніж потрібно, нулі додаються в кінці: ```js run let num = 12.34; @@ -189,7 +192,7 @@ alert( num.toString(2) ); // 11111111 ## Неточні розрахунки -Із середини, число представлено у 64-бітному форматі [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), тому для його зберігання треба саме 64 біти: 52 з них використовуються для зберігання цифр, 11 -- відповідають за позицію десяткової крапки (для цілих чисел вони дорівнюють нулю), а 1 біт -- для знака. +Із середини, число представлено у 64-бітному форматі [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), тому для його зберігання треба саме 64 біти: 52 з них використовуються для зберігання цифр, 11 -- відповідають за позицію десяткової крапки (для цілих чисел вони дорівнюють нулю), а 1 біт -- для знака. Якщо число занадто велике, та переповнює 64-біти, воно буде перетворене на спеціальне числове значення `Infinity`(Нескінченність): @@ -246,7 +249,7 @@ PHP, Java, C, Perl, Ruby дають абсолютно однаковий рез ```js run let sum = 0.1 + 0.2; -alert( sum.toFixed(2) ); // 0.30 +alert( sum.toFixed(2) ); // "0.30" ``` Зауважте, що `toFixed` завжди повертає рядок, щоб число гарантовано мало дві цифри після десяткової крапки. Це насправді зручно, якщо у нас є електронні покупки та нам потрібно показати `$0.30`. В інших випадках ми можемо використовувати одинарний плюс, щоб для приведення його до числа: @@ -305,7 +308,7 @@ JavaScript не викликає помилку в таких випадках. alert( isNaN("str") ); // true ``` - Але чи потрібна нам ця функція? Чи не можемо ми просто використати порівняння `=== NaN`? Вибачте, але відповідь - ні. Значення `NaN` унікальне тим, що воно нічому не дорівнює, включаючи себе: + Але чи потрібна нам ця функція? Чи не можемо ми просто використати порівняння `=== NaN`? Вибачте, але відповідь -- ні. Значення `NaN` унікальне тим, що воно нічому не дорівнює, включаючи себе: ```js run alert( NaN === NaN ); // false @@ -331,15 +334,44 @@ alert( isFinite(num) ); Зауважте, що порожній рядок, або рядок з пробілів трактується як `0` у всіх числових функціях, включаючи `isFinite`. +````smart header="`Number.isNaN` і `Number.isFinite`" +Методи [Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) і [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) є більш "суворими" версіями функцій `isNaN` і `isFinite`. Вони не перетворюють свій аргумент автоматично на число, а перевіряють, чи належить він до типу `number`. + +- `Number.isNaN(value)` повертає `true`, якщо аргумент належить до типу `number` і має значення `NaN`. У будь-якому іншому випадку він повертає `false`. + + ```js run + alert( Number.isNaN(NaN) ); // true + alert( Number.isNaN("str" / 2) ); // true + + // Зверніть увагу на різницю: + alert( Number.isNaN("str") ); // false, тому що "str" це рядок, а не число + alert( isNaN("str") ); // true, оскільки isNaN перетворює рядок "str" ​​на число та отримує NaN як результат цього перетворення + ``` + +- `Number.isFinite(value)` повертає `true`, якщо аргумент належить до типу `number` і не є `NaN/Infinity/-Infinity`. У будь-якому іншому випадку він повертає `false`. + + ```js run + alert( Number.isFinite(123) ); // true + alert( Number.isFinite(Infinity) ); // false + alert( Number.isFinite(2 / 0) ); // false + + // Зверніть увагу на різницю: + alert( Number.isFinite("123") ); // false, тому що "123" це рядок, а не число + alert( isFinite("123") ); // true, оскільки isFinite перетворює рядок "123" на число 123 + ``` + +У певному сенсі `Number.isNaN` і `Number.isFinite` простіші та зрозуміліші, ніж функції `isNaN` і `isFinite`. Однак на практиці переважно використовуються `isNaN` і `isFinite`, оскільки вони коротші для написання. +```` + ```smart header="Порівняння з `Object.is`" Існує спеціальний вбудований метод `Object.is`, який порівнює значення як `===`, але є більш надійним для двох виключень: 1. Працює з `NaN`: `Object.is(NaN, NaN) === true`, і це добре. -2. Значення `0` і` -0` різні: `Object.is(0, -0) === false`, технічно це правда, оскільки внутрішньо число має біт знаків, який може бути різним, навіть якщо всі інші біти - нулі. +2. Значення `0` і` -0` різні: `Object.is(0, -0) === false`, технічно це правда, оскільки внутрішньо число має біт знаків, який може бути різним, навіть якщо всі інші біти -- нулі. У всіх інших випадках `Object.is(a, b)` те саме, що `a === b`. -Цей спосіб порівняння часто використовується у специфікації JavaScript. Коли для внутрішнього алгоритму потрібно порівняти два значення, щоб вони були абсолютно однаковими, він використовує `Object.is` (ще його називають [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). +Ми згадуємо тут `Object.is`, оскільки він часто використовується в специфікації JavaScript. Коли для внутрішнього алгоритму потрібно порівняти два значення, щоб вони були абсолютно однаковими, він використовує `Object.is` (ще його називають [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). ``` @@ -429,6 +461,13 @@ JavaScript має вбудований [Math](https://developer.mozilla.org/uk/d - `parseInt(str, base)` розбирає рядок `str` на ціле число чисельної системи із заданим `base`, `2 ≤ base ≤ 36`. - `num.toString(base)` перетворює число в рядок в системі числення за допомогою заданої `base`. +Для регулярних тестів чисел: + +- `isNaN(value)` перетворює свій аргумент на число, а потім перевіряє його на `NaN` +- `Number.isNaN(value)` перевіряє, чи належить його аргумент до типу `number`, і якщо так, перевіряє його на `NaN` +- `isFinite(value)` перетворює свій аргумент на число, а потім перевіряє, чи не є `NaN/Infinity/-Infinity` +- `Number.isFinite(value)` перевіряє, чи належить його аргумент до типу `number`, і якщо так, перевіряє, чи не є `NaN/Infinity/-Infinity` + Для перетворення значень на зразок `12pt` та `100px` у число: - Використовуйте `parseInt/parseFloat` для "не суворого" перетворення, яке зчитує число з рядка, а потім повертає значення, яке вдалося прочитати перед помилкою. diff --git a/1-js/05-data-types/03-string/1-ucfirst/solution.md b/1-js/05-data-types/03-string/1-ucfirst/solution.md index d3feb21f3..9b6ac2045 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/solution.md +++ b/1-js/05-data-types/03-string/1-ucfirst/solution.md @@ -8,12 +8,7 @@ let newStr = str[0].toUpperCase() + str.slice(1); Але є невелика проблема. Якщо `str` порожній рядок, то `str[0]` буде `undefined`, а оскільки `undefined` не має методу `toUpperCase()`, ми отримаємо помилку. -Тут є два варіанти: - -1. Використати `str.charAt(0)`, оскільки він завжди повертає рядок (навіть для порожнього рядка). -2. Додати перевірку на порожній рядок. - -Ось 2-й варіант: +Найпростіший спосіб -- додати перевірку на порожній рядок, наприклад ось так: ```js run demo function ucFirst(str) { @@ -24,4 +19,3 @@ function ucFirst(str) { alert( ucFirst("василь") ); // Василь ``` - diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index f32ee9de1..4fe802c81 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -50,7 +50,7 @@ let guestList = "Гості: // Помилка: Unexpected token ILLEGAL Одинарні та подвійні лапки беруть свій початок з давніх часів створення мови, коли не було потреби у багатолінійних рядках. Зворотні лапки зʼявилися набагато пізніше і тому є більш універсальними. -Зворотні лапки також дозволяють нам задати "шаблонну функцію" перед першими зворотніми лапками. Синтаксис такий: func`string`. Функція `func` викликається автоматично, отримує рядок і вбудовані в неї вирази і може їх обробити. Це називається "теговим шаблоном". Ця функція полегшує реалізацію користувацької шаблонізації, але рідко використовується на практиці. Детальніше про це можна прочитати в [посібнику](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). +Зворотні лапки також дозволяють нам задати "шаблонну функцію" перед першими зворотніми лапками. Синтаксис такий: func`string`. Функція `func` викликається автоматично, отримує рядок і вбудовані в неї вирази і може їх обробити. Це називається "теговим шаблоном", це рідко використовується на практиці, але ви можете прочитати детальніше про це на MDN: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). ## Спеціальні символи @@ -59,7 +59,7 @@ let guestList = "Гості: // Помилка: Unexpected token ILLEGAL ```js run let guestList = "Гості:\n * Іван\n * Петро\n * Марія"; -alert(guestList); // список гостей в декілька рядків +alert(guestList); // список гостей в декілька рядків, як і вище ``` Наприклад, ці два рядки рівнозначні, просто написані по-різному: @@ -76,31 +76,24 @@ alert(str1 == str2); // true Є й інші, менш поширені "спеціальні" символи. -Ось повний список: - | Символ | Опис | |-----------|-------------| |`\n`|Розрив рядка| |`\r`|У текстових файлах Windows комбінація двох символів `\r\n` являє собою розрив рядка, тоді як в інших ОС, це просто `\n`. Так склалось з історичних причин, більшість ПЗ під Windows також розуміє `\n`| -|`\'`, `\"`|Лапки| +|`\'`, `\"`, \\`|Лапки| |`\\`|Зворотний слеш| |`\t`|Знак табуляції| -|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab – зберігаються для зворотної сумісності, зараз не використовуються| -|`\xXX`|Символ з шістнадцятковим юнікодним кодом `'\x7A'` -- це те ж саме що і `'z'`| -|`\uXXXX`|Символ з шістнадцятковим юнікодним кодом `XXXX` в кодуванні UTF-16, наприклад `\u00A9` -- це юнікодний символ для знаку копірайту `©`. Шістнадцятковий код обовʼязково має складатись з 4 символів| -|`\u{X…XXXXXX}` (від 1 до 6 шістнадцяткових символів)|Юнікодний символ в кодуванні UTF-32. Деякі рідкісні символи кодуються двома юнікодними символами, що займають 4 байти. Таким чином ми можемо вставляти довгі коди| +|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- зберігаються для зворотної сумісності, зараз не використовуються| + +Усі спеціальні символи починаються зі зворотного слеша `\`. Його також називають "символом екранування". -Приклади з Юнікодом: +Оскільки це так особливо, якщо нам потрібно показати зворотний слеш `\` у рядку, нам потрібно подвоїти його: ```js run -alert( "\u00A9" ); // © -alert( "\u{20331}" ); // 佫, рідкісний китайський ієрогліф (довгий юнікод) -alert( "\u{1F60D}" ); // 😍, емодзі посмішки з очима в формі сердець (інший довгий юнікод) +alert( `Зворотний слеш: \\` ); // Зворотний слеш: \ ``` -Усі спеціальні символи починаються зі зворотного слеша `\`. Його також називають "символом екранування". - -Ми також можемо його використати, якщо хочемо вставити лапки в рядок. +Так звані "екрановані" лапки `\'`, `\"`, \\` використовуються для вставки цих лапок в рядок, який обмежено таким же типом лапок. Наприклад: @@ -116,15 +109,7 @@ alert( 'Ім*!*\'*/!*я моє — Морж!' ); // *!*Ім'я*/!* моє — М alert( `Ім'я моє — Морж!` ); // Ім'я моє — Морж! ``` -Зверніть увагу, що зворотний слеш `\` в JavaScript служить для правильного зчитування рядка. Рядок в памʼяті не містить `\`. Ви можете чітко це побачити у `alert` з наведених вище прикладів. - -Але що, якщо нам потрібно показати зворотний слеш `\` всередині рядка? - -Це можна зробити додаванням ще одного зворотного слеша `\\`: - -```js run -alert( `Зворотний слеш: \\` ); // Зворотний слеш: \ -``` +Окрім цих спеціальних символів, існує також спеціальна нотація для кодів Unicode `\u…`, вона використовується рідко та описана в додатковому розділі про [Unicode](info:unicode). ## Довжина рядка @@ -139,12 +124,12 @@ alert( `Моє\n`.length ); // 4 ```warn header="`length` -- це властивість" Люди з досвідом роботи в інших мовах випадково намагаються викликати властивість, додаючи круглі дужки: вони пишуть `str.length()` замість `str.length`. Це не спрацює. -Зверніть увагу, що `str.length` -- це числове значення, а не функція, додавати дужки не потрібно. +Зверніть увагу, що `str.length` -- це числове значення, а не функція, додавати дужки не потрібно. Не `.length()`, а `.length`. ``` ## Доступ до символів -Отримати символ, котрий займає позицію `pos`, можна за допомогою квадратних дужок: `[pos]`, або викликати метод [str.charAt(pos)](mdn:js/String/charAt). Перший символ займає нульову позицію. +Отримати символ, котрий займає позицію `pos`, можна за допомогою квадратних дужок: `[pos]`, або викликати метод [str.at(pos)](mdn:js/String/at). Перший символ займає нульову позицію: ```js run let str = `Привіт`; @@ -155,17 +140,20 @@ alert( str.charAt(0) ); // П // останній символ alert( str[str.length - 1] ); // т +alert( str.at(-1) ); ``` -Квадратні дужки -- це сучасний спосіб отримати символ, тоді як `charAt` існує більше завдяки історичним причинам. +Як бачите, перевага методу `.at(pos)` полягає в тому, що він допускає від'ємну позицію. Якщо `pos` від'ємне число, тоді позиція відраховується з кінця рядка. + +Отже, `.at(-1)` означає останній символ, а `.at(-2)` -- передостанній, тощо. -Різниця між ними лише в тому, що якщо символ з такою позицією відсутній, тоді `[]` поверне `undefined`, а `charAt` -- порожній рядок: +Квадратні дужки завжди повертають `undefined` для від'ємних індексів, наприклад: ```js run let str = `Привіт`; -alert( str[1000] ); // undefined -alert( str.charAt(1000) ); // '' (порожній рядок) +alert( str[-2] ); // undefined +alert( str.at(-2) ); // і ``` Ми також можемо перебрати рядок посимвольно, використовуючи `for..of`: @@ -214,8 +202,8 @@ alert( 'Інтерфейс'.toLowerCase() ); // інтерфейс Або якщо ми хочемо перенести в нижній регістр конкретний символ: -```js -alert( 'Інтерфейс'[0].toLowerCase() ); // 'і' +```js run +alert( 'Interface'[0].toLowerCase() ); // 'і' ``` ## Пошук підрядка @@ -310,45 +298,6 @@ if (str.indexOf("Віджет") != -1) { } ``` -#### Трюк з побітовим НЕ - -Один зі старих прийомів, який тут використовується, це [побітовий оператор НЕ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT) -- `~`. Він перетворює число в 32-розрядне ціле число (вилучає десяткову частину, якщо вона є), а потім повертає всі біти в його двійковому представленні. - -На практиці це означає просту річ: для 32-розрядних чисел значення `~n` рівне `-(n+1)`. - -Наприклад: - -```js run -alert( ~2 ); // -3, те саме що -(2+1) -alert( ~1 ); // -2, те саме що -(1+1) -alert( ~0 ); // -1, те саме що -(0+1) -*!* -alert( ~-1 ); // 0, те саме що -(-1+1) -*/!* -``` - -Як ми можемо бачити, `~n` рівне 0 лише при `n == -1` (для будь якого 32-розрядного цілого числа `n`) - -Відповідно, проходження перевірки `if ( ~str.indexOf("...") )` означає, що результат `indexOf` відрізняється від `-1`. Іншими словами, коли є збіг. - -Таку перевірку іноді використовують як компактний `indexOf`: - -```js run -let str = "Віджет"; - -if (~str.indexOf("Віджет")) { - alert( 'Є співпадіння!' ); // Працює -} -``` - -Зазвичай використовувати можливості мови неочевидним способом не рекомендується, але цей трюк широко використовується в старому коді, тому це важливо розуміти. - -Просто запамʼятайте: `if (~str.indexOf(...))` означає "якщо знайдено". - -Проте, якщо бути точніше, через те, що великі числа обрізаються до 32 бітів оператором `~`, існують числа, для яких результат також буде `0`, найменше таке число `~4294967295=0`. Тому така перевірка буде працювати для рядків невеликої довжини. - -Зараз такий трюк ми можемо побачити лише в старому коді, тому що в сучасному JavaScript є метод `.includes`. - ### includes, startsWith, endsWith Сучасніший метод [str.includes(substr, pos)](mdn:js/String/includes) повертає `true/false` в залежності від того чи є `substr` в рядку `str`. @@ -371,8 +320,8 @@ alert( "Віджет".includes("ід", 3) ); // false, починаючи з 3- Відповідно, методи [str.startsWith](mdn:js/String/startsWith) та [str.endsWith](mdn:js/String/endsWith) перевіряють, чи починається і чи закінчується рядок певним підрядком. ```js run -alert( "Віджет".startsWith("Від") ); // true, "Віджет" починається з "Від" -alert( "Віджет".endsWith("жет") ); // true, "Віджет" закінчується підрядком "жет" +alert( "*!*Від*/!*жет".startsWith("Від") ); // true, "Віджет" починається з "Від" +alert( "Від*!*жет*/!*".endsWith("жет") ); // true, "Віджет" закінчується підрядком "жет" ``` ## Отримання підрядка @@ -407,9 +356,9 @@ alert( "Віджет".endsWith("жет") ); // true, "Віджет" закінч ``` `str.substring(start [, end])` -: Повертає частину рядка *між* `start` та `end`. +: Повертає частину рядка *між* `start` та `end` (не включаючи `end`).. - Цей метод майже такий самий що і `slice`, але він дозволяє задати `start` більше ніж `end`. + Цей метод майже такий самий що і `slice`, але він дозволяє задати `start` більше ніж `end` (у цьому випадку він просто міняє значення `start` і `end` місцями). Наприклад: @@ -445,18 +394,22 @@ alert( "Віджет".endsWith("жет") ); // true, "Віджет" закінч alert( str.substr(-4, 2) ); // 'gi', починаючи з позиції 4 з кінця отримуєму 2 символа ``` + Цей метод міститься в [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) специфікації мови. Це означає, що лише рушії браузерного Javascript мають його підтримувати, і не рекомендується його використовувати. На практиці це підтримується всюди. + Давайте підсумуємо ці методи щоб не заплутатись: | Метод | вибирає... | відʼємні значення | |--------|-----------|-----------| -| `slice(start, end)` | від `start` до `end` (`end` не включно) | дозволяє відʼємні значення | -| `substring(start, end)` | між `start` та `end` | відʼємні значення інтерпретуються як `0` | +| `slice(start, end)` | від `start` до `end` (не включаючи `end`) | дозволяє відʼємні значення | +| `substring(start, end)` | між `start` та `end` (не включаючи `end`) | відʼємні значення інтерпретуються як `0` | | `substr(start, length)` | `length` символів від `start` | дозволяє відʼємні значення `start` | ```smart header="Який метод вибрати?" Усі вони можуть виконати задачу. Формально `substr` має незначний недолік: він описаний не в основній специфікації JavaScript, а в Annex B, який охоплює лише функції браузера, які існують переважно з історичних причин. Тому не браузерні середовища, можуть не підтримувати його. Але на практиці це працює всюди. -З двох інших варіантів `slice` дещо гнучкіший, він допускає негативні аргументи та коротший в записі. Отже, достатньо запамʼятати лише `slice` з цих трьох методів. +З двох інших варіантів `slice` дещо гнучкіший, він допускає від'ємні аргументи та коротший в записі. + +Отже, достатньо запамʼятати лише `slice` з цих трьох методів. ``` ## Порівняння рядків @@ -479,17 +432,19 @@ alert( "Віджет".endsWith("жет") ); // true, "Віджет" закінч Це може призвести до дивних результатів, якщо ми відсортуємо ці назви країн. Зазвичай люди очікують, що `Zealand` буде після `Österreich`. -Щоб зрозуміти, що відбувається, давайте розглянемо внутрішнє представлення рядків у JavaScript. +Щоб зрозуміти, що відбувається, давайте розглянемо внутрішнє представлення рядків у JavaScript закодованих за допомогою [UTF-16](https://uk.wikipedia.org/wiki/UTF-16). Тобто: кожен символ має відповідний числовий код.. -Усі рядки кодуються за допомогою [UTF-16](https://uk.wikipedia.org/wiki/UTF-16). Тобто: кожен символ має відповідний цифровий код. Існують спеціальні методи, які дозволяють отримати символ для коду і навпаки. +Існують спеціальні методи, які дозволяють отримати символ по коду і навпаки. `str.codePointAt(pos)` -: Повертає код символу на позиції `pos`: +: Повертає десяткове число, що є кодом символу на позиції `pos`: ```js run // літери в різному регістрі мають різні коди 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 +452,7 @@ alert( "Віджет".endsWith("жет") ); // true, "Віджет" закінч ```js run alert( String.fromCodePoint(90) ); // Z - ``` - - Ми також можемо додати юнікодні символи за їхніми кодами, використовуючи `\u`, за яким слідує шістнадцятковий код: - - ```js run - // 90 – це 5a в шістнадцятковій системі числення - alert( '\u005a' ); // Z + alert( String.fromCodePoint(0x5a) ); // Z (ми також можемо використовувати шістнадцяткове значення як аргумент) ``` Тепер давайте подивимося на символи з кодами `65..220` (латинський алфавіт і трохи більше), створивши з них рядок: @@ -515,6 +464,7 @@ for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); +// Output: // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` @@ -534,7 +484,7 @@ alert( str ); Отже, браузеру потрібно знати, яку мову використовувати для порівняння. -На щастя, усі сучасні браузери (IE10- вимагає додаткової бібліотеки [Intl.js](https://github.com/andyearnshaw/Intl.js/)) підтримують стандарт інтернаціоналізації [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). +На щастя, усі сучасні браузери підтримують стандарт інтернаціоналізації [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/). Він забезпечує спеціальний метод для порівняння рядків різними мовами, дотримуючись їхніх правил. @@ -552,119 +502,11 @@ alert( 'Österreich'.localeCompare('Zealand') ); // -1 Цей метод насправді має два додаткові аргументи, зазначені в [документації](mdn:js/String/localeCompare), що дозволяє йому вказати мову (типово взяту з середовища, порядок букв залежить від мови) і встановити додаткові правила, як-от чутливість до регістру або чи слід розглядати різницю між `"a"` та `"á"`. -## Як усе влаштовано, Юнікод - -```warn header="Поглиблення в тему" -Розділ заглиблюється у внутрішню частину рядків. Ці знання знадобляться вам, якщо ви плануєте мати справу з емодзі, рідкісними математичними чи ієрогліфічними символами чи іншими рідкісними символами. - -Ви можете пропустити розділ, якщо не плануєте підтримувати їх. -``` - -### Сурогатні пари - -Усі часто використовувані символи мають 2-байтові коди. Літери в більшості європейських мов, цифри і навіть більшість ієрогліфів мають 2-байтове представлення. - -Але 2 байти – це лише 65536 комбінацій, і цього недостатньо для кожного можливого символу. Тому рідкісні символи кодуються парою 2-байтових символів, які називаються «сурогатною парою». - -Довжина таких символів – `2`: - -```js run -alert( '𝒳'.length ); // 2, математичний символ "x" у верхньому регістрі -alert( '😂'.length ); // 2, емодзі -- обличчя зі сльозами радості -alert( '𩷶'.length ); // 2, рідкісний китайський ієрогліф -``` - -Зауважте, що сурогатні пари не існували на момент створення JavaScript, і тому мова не обробляє їх належним чином! - -Насправді ми маємо один символ у кожному з наведених вище рядків, але `length` показує довжину `2`. - -`String.fromCodePoint` і `str.codePointAt` – це кілька рідкісних методів, які правильно працюють із сурогатними парами. Вони зʼявились в мові нещодавно. До них були лише [String.fromCharCode](mdn:js/String/fromCharCode) і [str.charCodeAt](mdn:js/String/charCodeAt). Ці методи насправді такі ж, як `fromCodePoint/codePointAt`, але не працюють із сурогатними парами. - -Отримати символ може бути складно, оскільки сурогатні пари розглядаються як два символи: - -```js run -alert( '𝒳'[0] ); // дивні символи... -alert( '𝒳'[1] ); // ...частини сурогатної пари -``` - -Зверніть увагу, що частини сурогатної пари не мають значення один без одного. Отже, сповіщення в прикладі вище насправді відображають сміття. - -Технічно, сурогатні пари також можна виявити за їх кодами: якщо символ має код в інтервалі `0xd800..0xdbff`, то це перша частина сурогатної пари. Наступний символ (друга частина) повинен мати код в інтервалі `0xdc00..0xdfff`. За стандартом ці інтервали зарезервовані виключно для сурогатних пар. - -У наведеному вище випадку: - -```js run -// charCodeAt не підтримує сурогатні пари, тому дає коди для частин - -alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, між 0xd800 та 0xdbff -alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, між 0xdc00 та 0xdfff -``` - -Більше способів роботи із сурогатними парами ви знайдете пізніше в розділі . Для цього також є спеціальні бібліотеки, але немає достатньо відомої, щоб запропонувати її тут. - -### Діакритичні знаки та нормалізація - -У багатьох мовах є символи, які складаються з основного символу з позначкою над/під ним. - -Наприклад, буква `a` може бути базовим символом для: `àáâäãåā`. Найбільш поширені "складені" символи мають власний код у таблиці UTF-16. Але не всі, оскільки можливих комбінацій занадто багато. - -Для підтримки довільних композицій UTF-16 дозволяє нам використовувати кілька юнікодних символів: основний символ, за яким слідує один або багато символів «позначок», які «прикрашають» його. - -Наприклад, якщо у нас є `S`, за яким слідує спеціальний символ "крапка зверху" (код `\u0307`), він відображається як Ṡ. - -```js run -alert( 'S\u0307' ); // Ṡ -``` - -Якщо нам потрібна додаткова позначка над літерою (або під нею) -- не проблема, просто додайте потрібний символ позначки. - -Наприклад, якщо ми додамо символ "крапка внизу" (код `\u0323`), то матимемо "S з крапками зверху і знизу": `Ṩ`. - -Наприклад: - -```js run -alert( 'S\u0307\u0323' ); // Ṩ -``` - -Це забезпечує велику гнучкість, але також є цікава проблема: два символи візуально можуть виглядати однаково, але представлені різними юнікодними композиціями. - -Наприклад: - -```js run -let s1 = 'S\u0307\u0323'; // Ṩ, S + крапка зверху + крапка знизу -let s2 = 'S\u0323\u0307'; // Ṩ, S + крапка знизу + крапка зверху - -alert( `s1: ${s1}, s2: ${s2}` ); - -alert( s1 == s2 ); // false, хоча на вигляд символи однакові (?!) -``` - -Щоб вирішити це, існує алгоритм "юнікодної нормалізації", який приводить кожен рядок до єдиної "нормальної" форми. - -Це реалізовано за допомогою [str.normalize()](mdn:js/String/normalize). - -```js run -alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true -``` - -Цікаво, що в нашій ситуації `normalize()` насправді об’єднує послідовність з 3 символів в один: `\u1e68` (S з двома крапками). - -```js run -alert( "S\u0307\u0323".normalize().length ); // 1 - -alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true -``` - -Насправді це не завжди так. Причина в тому, що символ `Ṩ` є "досить поширеним", тому розробники UTF-16 включили його в основну таблицю і присвоїли йому код. - -Якщо ви хочете дізнатися більше про правила та варіанти нормалізації -- вони описані в додатку до стандарту Unicode: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), але для більшості практичних для цілей інформації з цього розділу достатньо. - ## Підсумки -- Є 3 види лапок. Зворотні лапки дозволяють рядку охоплювати кілька ліній і вбудовувати вирази `${…}`. -- Рядки в JavaScript кодуються за допомогою UTF-16. -- Ми можемо використовувати спеціальні символи, такі як `\n`, і вставляти літери за допомогою їхнього юнікоду за допомогою `\u...`. -- Щоб отримати символ, використовуйте: `[]`. +- Є 3 види лапок. Зворотні лапки дозволяють рядку охоплювати кілька ліній і застосовувати вбудовувані вирази `${…}`. +- Ми можемо використовувати спеціальні символи, такі як розрив рядка `\n`. +- Щоб отримати символ, використовуйте: `[]` або метод `at`. - Щоб отримати підрядок, використовуйте: `slice` або `substring`. - Щоб перевести рядок у нижній/верхній регістри, використовуйте: `toLowerCase/toUpperCase`. - Щоб знайти підрядок, використовуйте: `indexOf`, або `includes/startsWith/endsWith` для простих перевірок. @@ -677,3 +519,5 @@ alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true - ...та багато іншого можна знайти в [посібнику](mdn:js/String). Рядки також мають методи пошуку/заміни регулярними виразами. Але це велика тема, тому пояснюється в окремому розділі . + +Крім того, на даний момент важливо знати, що рядки базуються на кодуванні Unicode, і тому виникають проблеми з порівнянням, які ми описали вище. Більше про Unicode у розділі . diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md index ca89aee31..af2f7b4a6 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md +++ b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md @@ -59,7 +59,7 @@ alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 Таке рішення має оцінку часу виконання [O(n2)](https://uk.wikipedia.org/wiki/Нотація_Ландау). Інакше кажучи, якщо ми збільшимо розмір масиву вдвічі, алгоритм буде виконуватися в 4 рази довше. -Для великих масивів (1000, 10000 або більше елементів) подібні алгоритми можуть призводити до серйозних "пригальмувань" роботі. +Для великих масивів (1000, 10000 або більше елементів) подібні алгоритми можуть призводити до серйозних "пригальмувань" в роботі. # Швидке рішення @@ -91,4 +91,4 @@ alert( getMaxSubSum([-1, -2, -3]) ); // 0 Цей алгоритм потребує рівно один прохід по масиву, його оціночний час виконання - O(n). -Ви можете дізнатися більше про цей алгоритм тут: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). Якщо досі не цілком зрозуміло, як це працює, будь ласка, подивіться алгоритм у прикладах вище, це буде краще за будь-які слова. +Ви можете дізнатися більше про цей алгоритм тут: [Maximum subarray problem](https://en.wikipedia.org/wiki/Maximum_subarray_problem). Якщо досі не зрозуміло, як це працює, будь ласка, подивіться алгоритм у прикладах вище, це буде краще за будь-які слова. diff --git a/1-js/05-data-types/04-array/2-create-array/task.md b/1-js/05-data-types/04-array/2-create-array/task.md index 231504f27..c2043ebb3 100644 --- a/1-js/05-data-types/04-array/2-create-array/task.md +++ b/1-js/05-data-types/04-array/2-create-array/task.md @@ -8,7 +8,7 @@ importance: 5 1. Створіть масив `styles` з елементами "Jazz" та "Blues". 2. Додайте "Rock-n-Roll" в кінець масиву. -3. Замініть значення в середині масиву на "Classics". Ваш код повинен шукати медіанний елемент у масивах будь-якої довжини. +3. Замініть значення в середині масиву на "Classics". Ваш код для пошуку медіанного елемента має працювати для будь-яких масивів непарної довжини. 4. Видаліть перший елемент масиву та покажіть його. 5. Вставте `Rap` та `Reggae` на початок масиву. diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index c7906ae3d..a6e182105 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -155,7 +155,7 @@ alert( fruits.at(-1) ); // Plum Масиви в JavaScript можуть працювати як стеки і як черги. Ми можемо додавати/видаляти елементи як на початку так і у кінці масиву. -В комп’ютерних науках структури даних, які дозволяють це робити, мають назву «[двобічна черга](https://uk.wikipedia.org/wiki/Двобічна_черга)». +В комп’ютерних науках структури даних, які дозволяють це робити, мають назву [двобічна черга](https://uk.wikipedia.org/wiki/Двобічна_черга). **Методи, які працюють з кінцем масиву:** @@ -512,22 +512,27 @@ alert('0' == '' ); // false, тут немає конвертації типів Масив -- це особливий вид об’єкту, створений для зберігання та обробки _впорядкованих елементів_. -- Оголошення: +Оголошення массиву: - ```js - // квадратні дужки (як правило) - let arr = [item1, item2...]; +```js +// квадратні дужки (як правило) +let arr = [item1, item2...]; - // new Array (набагато рідше) - let arr = new Array(item1, item2...); - ``` +// new Array (набагато рідше) +let arr = new Array(item1, item2...); +``` - Виклик `new Array(number)` створює масив з заданою довжиною, але без елементів. +Виклик `new Array(number)` створює масив з заданою довжиною, але без елементів. - Властивість `length` демонструє довжину масиву або, якщо точніше, останній цифровий індекс масиву плюс один. Це виконується автоматично методами масиву. - Якщо ми вручну скорочуємо `length`, масив зменшується (нагадаємо, що ця операція незворотня). -Ми можемо використовувати масив як двосторонню чергу за допомогою наступних оперцій: +Отримання елементів: + +- ми можемо отримати елемент за його індексом, ось так `arr[0]` +- також ми можемо використати метод `at(i)`, який допускає негативні індекси. Для негативних значень `i` він відступає від кінця масиву. Якщо `i >= 0`, він працює так само, як `arr[i]`. + +Ми можемо використовувати масив як двосторонню чергу за допомогою наступних операцій: - `push(...items)` додає `items` в кінець масиву. - `pop()` видаляє елемент з кінця масиву та повертає його. diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 9c0484d49..c4d9698f4 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -234,12 +234,13 @@ arr.forEach(function(item, index, array) { ### indexOf/lastIndexOf та includes -Методи [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) та [arr.includes](mdn:js/Array/includes) мають однаковий синтаксис і роблять по суті те ж саме, що і їх рядкові аналоги, але працюють з елементами замість символів: +Методи [arr.indexOf](mdn:js/Array/indexOf) та [arr.includes](mdn:js/Array/includes) мають однаковий синтаксис і роблять по суті те ж саме, що і їх рядкові аналоги, але працюють з елементами замість символів: -- `arr.indexOf(item, from)` -- шукає `item`, починаючи з індексу `from`, і повертає індекс, на якому був знайдений шуканий елемент, в іншому випадку -1. -- `arr.lastIndexOf(item, from)` -- те ж саме, але шукає справа наліво. +- `arr.indexOf(item, from)` -- шукає `item`, починаючи з індексу `from`, і повертає індекс, на якому був знайдений шуканий елемент, в іншому випадку `-1`. - `arr.includes(item, from)` -- шукає `item`, починаючи з індексу `from`, і повертає `true`, якщо пошук успішний. +Зазвичай ці методи використовуються лише з одним аргументом: `item` для пошуку. Типово пошук відбувається з самого початку. + Наприклад: ```js run @@ -252,19 +253,31 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` -Зверніть увагу, що методи використовують суворе порівняння `===`. Таким чином, якщо ми шукаємо `false`, він знаходить саме `false`, а не нуль. +Зверніть увагу, що метод `indexOf` використовує суворе порівняння `===`. Таким чином, якщо ми шукаємо `false`, він знаходить саме `false`, але не нуль. + +Якщо ми хочемо перевірити наявність `item` в массиві, і нема потреби знати його точний індекс, тоді краще використати `arr.includes`. + +Метод [arr.lastIndexOf](mdn:js/Array/lastIndexOf) такий самий, як `indexOf`, але шукає справа наліво. -Якщо ми хочемо перевірити наявність елемента, і нема потреби знати його точний індекс, тоді кращим є `arr.includes`. +```js run +let fruits = ['Apple', 'Orange', 'Apple'] + +alert( fruits.indexOf('Apple') ); // 0 (перший Apple) +alert( fruits.lastIndexOf('Apple') ); // 2 (останній Apple) +``` -Крім того, дуже незначною відмінністю є те, що він правильно обробляє `NaN` на відміну від `indexOf/lastIndexOf`: +````smart header="Метод `includes` правильно обробляє `NaN`" +Незначною, але вартою уваги властивістю `includes` є те, що він правильно обробляє `NaN`, на відміну від `indexOf`: ```js run const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (повинен бути 0, але === перевірка на рівність не працює з NaN) alert( arr.includes(NaN) );// true (вірно) ``` +That's because `includes` was added to JavaScript much later and uses the more up to date comparison algorithm internally. +```` -### find і findIndex +### find і findIndex/findLastIndex Уявіть, що у нас є масив обʼєктів. Як нам знайти обʼєкт за певною умовою? @@ -306,6 +319,25 @@ alert(user.name); // John Метод [arr.findIndex](mdn:js/Array/findIndex) -- по суті, те ж саме, але повертає індекс, на якому був знайдений елемент, а не сам елемент, і `-1`, якщо нічого не знайдено. +Метод [arr.findLastIndex](mdn:js/Array/findLastIndex) схожий на `findIndex`, але шукає справа наліво, подібно до `lastIndexOf`. + +Ось приклад: + +```js run +let users = [ + {id: 1, name: "John"}, + {id: 2, name: "Pete"}, + {id: 3, name: "Mary"}, + {id: 4, name: "John"} +]; + +// Знайдемо індекс першого John +alert(users.findIndex(user => user.name == 'John')); // 0 + +// Знайдемо індекс останнього John +alert(users.findLastIndex(user => user.name == 'John')); // 3 +``` + ### filter Метод `find` шукає один (перший) елемент, на якому функція-колбек поверне `true`. @@ -388,7 +420,8 @@ alert( arr ); // *!*1, 15, 2*/!* Щоб використовувати наш власний порядок сортування, нам потрібно надати функцію як аргумент `arr.sort()`. -Функція повинна для пари значень повертати: +Функція має порівняти два довільних значення та повернути: + ```js function compare(a, b) { if (a > b) return 1; // якщо перше значення більше за друге @@ -633,7 +666,6 @@ arr.reduce((sum, current) => sum + current); Метод [arr.reduceRight](mdn:js/Array/reduceRight) працює аналогічно, але проходить по масиву справа наліво. - ## Array.isArray Масиви не мають окремого типу в Javascript. Вони засновані на обʼєктах. @@ -733,7 +765,7 @@ alert(soldiers[1].age); // 23 - `reduce(func, initial)` -- обчислює одне значення на основі всього масиву, викликаючи `func` для кожного елемента і передаючи проміжний результат між викликами. - Додатково: - - `Array.isArray(arr)` перевіряє, чи є `arr` масивом. + - `Array.isArray(value)` перевіряє, чи є `value` масивом, якщо так, повертає `true`, інакше `false`. Зверніть увагу, що методи `sort`, `reverse` та `splice` змінюють поточний масив. @@ -745,7 +777,8 @@ alert(soldiers[1].age); // 23 Ці методи поводяться приблизно як оператори `||` та `&&`. Якщо `fn` повертає істинне значення, `arr.some()` негайно повертає `true` і припиняє ітерацію по решті елементів. Якщо `fn` повертає хибне значення, `arr.every()` негайно повертає `false` і припиняє ітерацію по решті елементів. -Ми можемо використовувати `every` для порівняння масивів: + Ми можемо використовувати `every` для порівняння масивів: + ```js run function arraysEqual(arr1, arr2) { return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 15dfe1abf..fb0f4ae2f 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -215,7 +215,7 @@ alert(arr.pop()); // Світ (метод працює) Те ж саме відбувається і з ітерованим об’єктом: -```js +```js run // припустимо, що діапазон взятий з наведеного вище прикладу let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (array toString conversion works) @@ -230,7 +230,7 @@ Array.from(obj[, mapFn, thisArg]) Наприклад: -```js +```js run // припустимо, що діапазон взятий з наведеного вище прикладу // порахуємо квадрат кожного числа diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index 2e0ee66cc..0bd23a5a8 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -10,17 +10,17 @@ ## Map -[Map](mdn:js/Map) це колекція ключ/значення, як і `Object`. Але основна відмінність полягає в тому, що `Map` дозволяє мати ключі будь-якого типу. +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- це колекція ключ/значення, як і `Object`. Але основна відмінність полягає в тому, що `Map` дозволяє мати ключі будь-якого типу. Методи та властивості: -- `new Map()` -- створює колекцію. -- `map.set(key, value)` -- зберігає значення `value` за ключем `key`. -- `map.get(key)` -- повертає значення за ключем; повертає `undefined` якщо `key` немає в колекції. -- `map.has(key)` -- повертає `true` якщо `key` існує, інакше `false`. -- `map.delete(key)` -- видаляє елемент по ключу. -- `map.clear()` -- видаляє всі елементи колекції. -- `map.size` -- повертає поточну кількість елементів. +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- створює колекцію. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- зберігає значення `value` за ключем `key`. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- повертає значення за ключем; повертає `undefined` якщо `key` немає в колекції. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- повертає `true` якщо `key` існує, інакше `false`. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- видаляє елемент (пару ключ/значення) за ключем. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- видаляє всі елементи колекції. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- повертає поточну кількість елементів. Наприклад: @@ -100,14 +100,13 @@ map.set('1', 'str1') ``` ```` - ## Перебір Map Для перебору колекції Map є 3 метода: -- `map.keys()` -- повертає об’єкт-ітератор для ключів, -- `map.values()` -- повертає об’єкт-ітератор для значень, -- `map.entries()` -- повертає об’єкт-ітератор зі значеннями виду [ключ, значення], цей варіант використовується за умовчанням у `for..of`. +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- повертає об'єкт-ітератор для ключів, +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- повертає об'єкт-ітератор для значень, +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- повертає об'єкт-ітератор зі значеннями виду [ключ, значення], цей варіант типово використовується з `for..of`. Наприклад: @@ -162,7 +161,7 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -Якщо у нас вже є звичайний об’єкт, і ми б хотіли створити з нього `Map`, то допоможе вбудований метод [Object.entries(obj)](mdn:js/Object/entries), котрий отримує об’єкт і повертає масив пар ключ-значення для нього, як раз в цьому форматі. +Якщо у нас вже є звичайний об’єкт, і ми б хотіли створити з нього `Map`, то допоможе вбудований метод [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries), котрий отримує об’єкт і повертає масив пар ключ-значення для нього, як раз в цьому форматі. Таким чином ми можемо створити `Map` з об’єкта наступним чином: @@ -233,16 +232,16 @@ let obj = Object.fromEntries(map); // прибрати .entries() ## Set -Об’єкт `Set` -- це особливий тип колекції: "множина" значень (без ключів), де кожне значення може з’являтися тільки раз. +Об’єкт [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- це особливий тип колекції: "множина" значень (без ключів), де кожне значення може з’являтися тільки раз. Основні методи: -- `new Set(iterable)` -- створює `Set`, якщо аргументом виступає об’єкт-ітератор, тоді значення копіюються в `Set`. -- `set.add(value)` -- додає нове значення до `Set`, повертає `Set`. -- `set.delete(value)` -- видаляє значення з `Set`, повертає `true`, якщо `value` наявне в множині значень на момент виклику методу, інакше `false`. -- `set.has(value)` -- повертає `true`, якщо `value` присутнє в множині `Set`, інакше `false`. -- `set.clear()` -- видаляє всі значення множини `Set`. -- `set.size` -- повертає кількість елементів в множині. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- створює `Set`, якщо аргументом виступає об’єкт-ітератор, тоді значення копіюються в `Set`. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- додає нове значення до `Set`, повертає `Set`. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- видаляє значення з `Set`, повертає `true`, якщо `value` наявне в множині значень на момент виклику методу, інакше `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- повертає `true`, якщо `value` присутнє в множині `Set`, інакше `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- видаляє всі значення множини `Set`. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- повертає кількість елементів в множині. Родзинкою `Set` є виклики `set.add(value)`, що повторюються з однаковими значеннями `value`. Повторні виклики цього методу не змінюють `Set`. Це причина того, що кожне значення з’являється тільки один раз. @@ -272,7 +271,7 @@ for (let user of set) { } ``` -Альтернативою множини `Set` може виступати масив для зберігання гостей і додатковий код для перевірки вже наявного елемента за допомогою [arr.find](mdn:js/Array/find). Але в цьому випадку буде гірша продуктивність, тому що `arr.find` проходить весь масив для перевірки наявності елемента. Множина `Set` краще оптимізована для перевірки унікальності. +Альтернативою множини `Set` може виступати масив для зберігання гостей і додатковий код для перевірки вже наявного елемента за допомогою [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). Але в цьому випадку буде гірша продуктивність, тому що `arr.find` проходить весь масив для перевірки наявності елемента. Множина `Set` краще оптимізована для перевірки унікальності. ## Перебір об’єкта Set @@ -295,38 +294,38 @@ set.forEach((value, valueAgain, set) => { `Set` має ті ж вбудовані методи, що і `Map`: -- `set.keys()` -- повертає об’єкт-ітератор для значень, -- `set.values()` -- те ж саме, що `set.keys()`, для сумісності з `Map`, -- `set.entries()` -- повертає об’єкт-ітератор для пар виду `[значення, значення]`, присутній для сумісності з `Map`. +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- повертає об’єкт-ітератор для значень, +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- те ж саме, що `set.keys()`, для сумісності з `Map`, +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- повертає об’єкт-ітератор для пар виду `[значення, значення]`, присутній для сумісності з `Map`. ## Підсумки -`Map` -- це колекція ключ/значення. +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- це колекція ключ/значення. Методи та властивості: -- `new Map([iterable])` -- створює колекцію, можна вказати `об’єкт-ітератор` (зазвичай масив) з пар `[ключ, значення]` для ініціалізації. - - `map.set(key, value)` -- записує по ключу `key` значення `value`. -- `map.get(key)` -- повертає значення по `key` або `undefined`, якщо ключ `key` відсутній. -- `map.has(key)` -- повертає `true`, якщо ключ `key` присутній в колекції, інакше `false`. -- `map.delete(key)` -- видаляє елемент по ключу `key`. Повертає `true`, якщо `key` існує на момент виклику функції, інакше `false`. -- `map.clear()` -- очищає колекцію від всіх елементів. -- `map.size` -- повертає поточну кількість елементів. +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- створює колекцію, можна вказати `об’єкт-ітератор` (зазвичай масив) з пар `[ключ, значення]` для ініціалізації. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- записує по ключу `key` значення `value`. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- повертає значення по `key` або `undefined`, якщо ключ `key` відсутній. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- повертає `true`, якщо ключ `key` присутній в колекції, інакше `false`. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- видаляє елемент по ключу `key`. Повертає `true`, якщо `key` існує на момент виклику функції, інакше `false`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- очищає колекцію від всіх елементів. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- повертає поточну кількість елементів. Відмінності від звичайного об’єкта `Object`: - Що завгодно може бути ключем, в тому числі і об’єкти. - Є додаткові методи, властивість `size`. -`Set` -- колекція унікальних значень, так звана «множина». +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- колекція унікальних значень, так звана "множина". Методи та властивості: -- `new Set([iterable])` -- створює `Set`, можна вказати `об’єкт-ітератор` (зазвичай масив). -- `set.add(value)` -- додає значення (якщо воно вже є, то нічого не робить), повертає той же об’єкт `set`. -- `set.delete(value)` -- видаляє значення, повертає `true` якщо `value` було в множині на момент виклику, інакше `false`. -- `set.has(value)` -- повертає `true`, якщо значення присутній в множині, інакше `false`. -- `set.clear()` -- видаляє всі наявні значення. -- `set.size` -- повертає кількість елементів у множині. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- створює `Set`, можна вказати `об’єкт-ітератор` (зазвичай масив). +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- додає значення (якщо воно вже є, то нічого не робить), повертає той же об’єкт `set`. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- видаляє значення, повертає `true` якщо `value` було в множині на момент виклику, інакше `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- повертає `true`, якщо значення присутній в множині, інакше `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- видаляє всі наявні значення. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- повертає кількість елементів у множині. Перебір `Map` і `Set` завжди здійснюється в порядку додавання елементів, так що не можна сказати, що це невпорядковані колекції, але поміняти порядок елементів або отримати елемент безпосередньо по його номеру не можна. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 2b5549062..e271185e4 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,8 +1,10 @@ + # WeakMap та WeakSet Як ми знаємо з розділу , рушій JavaScript зберігає значення в пам’яті, поки воно є "доступним" і потенційно може бути використаним. Наприклад: + ```js let john = { name: "Іван" }; @@ -54,13 +56,13 @@ john = null; // перезапишемо посилання */!* ``` -`WeakMap` -- принципово відрізняється в цьому аспекті. Він не перешкоджає збиранню сміття серед об’єктів, що є ключами. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) -- принципово відрізняється в цьому аспекті. Він не перешкоджає збиранню сміття серед об’єктів, що є ключами. Подивимося, що це означає на прикладах. ## WeakMap -Перша відмінність між `Map` та `WeakMap` -- це те, що ключі повинні бути об’єктами, а не примітивними значеннями: +Перша відмінність між [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) та [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) -- це те, що ключі повинні бути об’єктами, а не примітивними значеннями: ```js run let weakMap = new WeakMap(); @@ -94,10 +96,10 @@ john = null; // перезапишемо посилання `WeakMap` має лише такі методи: -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` +- [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) +- [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) +- [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) +- [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) Чому є таке обмеження? Це з технічних причин. Якщо об’єкт втратив всі інші посилання (наприклад, `john` у коді вище), то він буде автоматично видалений збирачем сміття. Але технічно немає точних вказівок *коли відбувається видалення*. @@ -182,6 +184,7 @@ function process(obj) { let result = /* розрахунки результату для */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -221,6 +224,7 @@ function process(obj) { let result = /* розрахувати результат для */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -242,11 +246,11 @@ obj = null; ## WeakSet -`WeakSet` поводитися аналогічно: +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) поводитися аналогічно: - Це аналог `Set`, але ми можемо додати лише об’єкти до `WeakSet` (не примітиви). - Об’єкт існує в наборі, коли він доступний з де-небудь ще. -- Так само як `Set`, він підтримує `add`, `has` і `delete`, але не підтримує `size`, `keys()` та ітерацію. +- Так само як `Set`, він підтримує [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) і [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), але не підтримує `size`, `keys()` та ітерацію. Будучи "слабким", він також служить зберігання додаткових даних. Але не для довільних даних, а для фактів "так/ні". Приналежність до `WeakSet` може означати щось про об’єкт. @@ -280,9 +284,9 @@ john = null; ## Підсумки -`WeakMap` -- це подібна до `Map` колекція, яка дозволяє використовувати лише об’єкти, як ключі і видаляє їх разом з пов’язаним значенням, коли вони стануть недоступними іншим засобам. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) -- це подібна до `Map` колекція, яка дозволяє використовувати лише об’єкти, як ключі і видаляє їх разом з пов’язаним значенням, коли вони стануть недоступними іншим засобам. -`WeakSet` -- це подібна до `Set` колекція, яка зберігає тільки об’єкти та видаляє їх після того, як вони стануть недоступними іншим засобам. +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) -- це подібна до `Set` колекція, яка зберігає тільки об’єкти та видаляє їх після того, як вони стануть недоступними іншим засобам. Їх основна перевага полягає у тому, що вони мають слабке посилання на об’єкти, тому вони можуть бути легко видаленими збирачем сміття. diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md index 64c375ee2..6b7710198 100644 --- a/1-js/05-data-types/11-date/1-new-date/solution.md +++ b/1-js/05-data-types/11-date/1-new-date/solution.md @@ -13,6 +13,6 @@ alert( d1 ); ```js run //new Date(datastring) -let d2 = new Date("February 20, 2012 03:12:00"); +let d2 = new Date("2012-02-20T03:12"); alert( d2 ); ``` diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index 6282d7448..33024e8f4 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -57,9 +57,9 @@ `new Date(year, month, date, hours, minutes, seconds, ms)` : Створює дату з заданими компонентами у місцевому часовому поясі. Тільки перші два аргументи обов’язкові. - - `year` має мати 4 цифри: `2013` -- це нормально, `98` -- ні. + - `year` має містити 4 цифри. Для сумісності також допускаються 2 цифри, які вважаються `19xx`, напр. `98` тут те саме, що й `1998`, але настійно рекомендується завжди використовувати 4 цифри. - Рахунок місяців починається з `0` (січня), до `11` (грудня). - - Параметр `date` насправді день місяця, якщо він відсутній, то береться "1". + - Параметр `date` насправді день місяця, якщо він відсутній, то береться `1`. - Якщо `hours/minutes/seconds/ms` відсутні, вони вважаються рівними `0`. Наприклад: @@ -376,7 +376,7 @@ for (let i = 0; i < 10; i++) { ```warn header="Будьте обережні, що робить мікробенчмаркінг" Сучасні рушії JavaScript застосовують багато оптимізацій. Вони можуть підналаштувати результати "штучних тестів" у порівнянні з "нормальним використанням", особливо коли ми тестуємо щось дуже мале, наприклад, як працює оператор, або вбудована функція. Отже, якщо ви серйозно хочете оцінити продуктивність, то, будь ласка, дослідіть, як працює рушій JavaScript. І тоді ви, мабуть, не потребуєте мікробенчмаркінгів взагалі. -Великий набір статей про V8 можна знайти на . +Великий набір статей про V8 можна знайти на . ``` ## Date.parse з рядка @@ -407,7 +407,7 @@ alert(ms); // 1327611110417 (timestamp) ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); -alert(date); +alert(date); ``` ## Підсумки diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index d7830ab65..971fcb695 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -41,7 +41,7 @@ let student = { age: 30, isAdmin: false, courses: ['html', 'css', 'js'], - wife: null + spouse: null }; *!* @@ -58,7 +58,7 @@ alert(json); "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], - "wife": null + "spouse": null } */ */!* @@ -450,7 +450,7 @@ let json = `{ Крім того, JSON не підтримує коментарі. Додавання коментаря до JSON робить його недійсним. -Існує інший формат, який називається [JSON5](http://json5.org/), що підтримує ключі не обернені в лапки, коментарі тощо. Але це окрема бібліотека, а не частина специфікації мови. +Існує інший формат, який називається [JSON5](https://json5.org/), що підтримує ключі не обернені в лапки, коментарі тощо. Але це окрема бібліотека, а не частина специфікації мови. Звичайний JSON є настільки строгим не тому, що його розробники ледачі, а тому, що дозволяє легко, надійно та дуже швидко реалізувати алгоритм кодування та читання. diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index 1f322ba01..2ee8470ba 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -37,4 +37,4 @@ P.S. Звичайно, формула є найшвидшим рішенням. Варіант з циклом є другим з точки зору швидкості. Як і у випадку рекурсії, в циклі ми сумуємо ті ж числа. Але рекурсія передбачає вкладені виклики та управління стеком. Це також займає ресурси, тому це повільніше. -P.P.S. Деякі рушії підтримують оптимізацію "хвостового виклику" (tail call): якщо рекурсивний виклик є останнім в функції (як у випадку з `sumTo`), то зовнішня функція не повинна відновлювати виконання, отже рушію не потрібно запам’ятовувати контекст виконання. Це зменшує використання пам’яті, тому підрахунок `sumTo(100000)` стає можливим. Але якщо рушій JavaScript не підтримує оптимізацію хвостового виклику (більшість з них не підтримує), то виникне помилка: максимальний розмір стека перевищиться, оскільки зазвичай є обмеження на загальний розмір стека. +P.P.S. Деякі рушії підтримують оптимізацію "хвостового виклику" ("tail call"): якщо рекурсивний виклик є останнім в функції, то зовнішня функція не повинна відновлювати виконання, отже рушію не потрібно запам’ятовувати контекст виконання. Це зменшує використання пам’яті. Але якщо рушій JavaScript не підтримує оптимізацію хвостового виклику (більшість з них не підтримує), то виникне помилка: максимальний розмір стека перевищиться, оскільки зазвичай є обмеження на загальний розмір стека. diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index f7c6eba8c..d36559caf 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -61,7 +61,7 @@ pow(2, 4) = 16 if n==1 = x / pow(x, n) = - \ + \ else = x * pow(x, n - 1) ``` @@ -285,7 +285,7 @@ function pow(x, n) { **Будь-яка рекурсія може бути переписана за допомогою циклу. Варіант з використанням циклу зазвичай може бути більш ефективним.** -... Але іноді переписати рішення на цикл нетривіально, особливо коли функція використовує різні рекурсивні підвиклики залежно від умов та поєднує їх результат або коли розгалуження є більш складним. Тому така оптимізація може бути непотрібна і повністю не варта зусиль. +...Але іноді переписати рішення на цикл нетривіально, особливо коли функція використовує різні рекурсивні підвиклики залежно від умов та поєднує їх результат або коли розгалуження є більш складним. Тому така оптимізація може бути непотрібна і повністю не варта зусиль. Рекурсія може дати коротший код, який легше зрозуміти та підтримувати.Оптимізація не потрібна в кожному місці, в основному нам потрібний хороший код, тому використовується рекурсія. diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md index 7e7f53418..965b7feea 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -23,7 +23,7 @@ function sum(a, b) { alert(sum(1, 2, 3, 4, 5)); ``` -Помилки "надмірних" аргументів у цьому випадку не буде. Але, звичайно, будуть враховані лише перші два. +Помилки "надмірних" аргументів у цьому випадку не буде. Але, звичайно, будуть враховані лише перші два, тому результатом у коді вище є `3`. Решту параметрів можна включити до визначення функції за допомогою трьох крапок `...` що передують імені масиву, який їх міститиме. Точки буквально означають "зібрати решту параметрів у масив". diff --git a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md index f02a75814..0dd54a75f 100644 --- a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md +++ b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md @@ -1,4 +1,6 @@ +importance: 5 +--- # Функція у if Подивіться на код. Яким буде результат виклику на останньому рядку? diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 8b30a64d9..97fdd1db6 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -58,7 +58,7 @@ alert(test); // ReferenceError: test не визначена Те саме і для циклів: змінна, оголошена за допомогою `var`, не може бути блочною або локальною всередині цикла: -```js +```js run for (var i = 0; i < 10; i++) { var one = 1; // ... @@ -170,7 +170,7 @@ sayHi(); ```js run function sayHi() { - alert(phrase); + alert(phrase); *!* var phrase = "Привіт"; diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md index 40307ca1b..5ff34c5f8 100644 --- a/1-js/06-advanced-functions/06-function-object/article.md +++ b/1-js/06-advanced-functions/06-function-object/article.md @@ -326,7 +326,7 @@ welcome(); // Привіт, Гість (вкладений виклик вико Тепер це працює, тому що назва `"func"` -- локальне і знаходиться в середині функції. Воно не береться ззовні (і не доступно звідти). Специфікація гарантує, що воно завжди посилається на поточну функцію. -Зовнішній код все ще має свою змінну `sayHi` або `welcome`. А `func` -- це "внутрішнє ім’я функції", яким функція може викликати себе зсередини. +Зовнішній код все ще має свою змінну `sayHi` або `welcome`. А `func` -- це "внутрішнє ім’я функції", яким функція може надійно викликати себе зсередини. ```smart header="Це не працює з Function Declaration" Функціональність з "внутрішньою назвою", що описана вище, доступна лише для Function Expression, а не для Function Declaration. Для Function Declaration немає синтаксису для додавання "внутрішньої" назви. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md index c9a345591..3a317d38c 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -27,7 +27,7 @@ let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...) : Затримка перед запуском, у мілісекундах (1000 мс = 1 секунда), типове значення -- 0. `arg1`, `arg2`... -: Аргументи, які передаються у функцію (не підтримується в IE9-) +: Аргументи, які передаються у функцію Наприклад, цей код викликає `sayHi()` через одну секунду: @@ -102,7 +102,7 @@ alert(timerId); // той самий ідентифікатор (не прийм Знову ж таки, немає загальної специфікації для цих методів, тому це нормально. -Для браузерів таймери описані в [розділі таймерів](https://www.w3.org/TR/html5/webappapis.html#timers) стандарту HTML5. +Для браузерів таймери описані в [розділі таймерів](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) стандарту HTML Living Standard. ## setInterval @@ -256,7 +256,7 @@ alert("Привіт"); Існують також розширені, пов’язані з браузером, випадки використання тайм-аутів з нульовою затримкою, про які ми поговоримо в цьому розділі . ````smart header="Нульова затримка насправді не дорівнює нулю (у браузері)" -У браузері є обмеження щодо того, як часто можуть запускатися вкладені таймери. Згідно зі [стандартом HTML5](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers): "після п’яти вкладених таймерів інтервал змушений бути не менше 4 мілісекунд.". +У браузері є обмеження щодо того, як часто можуть запускатися вкладені таймери. Згідно зі [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers): "після п’яти вкладених таймерів інтервал змушений бути не менше 4 мілісекунд.". Давайте продемонструємо, що це означає, на прикладі нижче. `setTimeout` викликає перепланований себе з нульовою затримкою. Кожен виклик запам’ятовує реальний час з попереднього виклику в масиві `times`. Як виглядають реальні затримки? Подивимось: @@ -297,6 +297,6 @@ setTimeout(function run() { Наприклад, таймер у браузері може сповільнитися з багатьох причин: - Процесор перевантажений. - Вкладка браузера знаходиться у фоновому режимі. -- Ноутбук працює від акумулятора. +- Ноутбук працює від акумулятора в режимі збереження заряду. Все це може збільшити мінімальний інтервал спрацьовування таймера (мінімальну затримку) до 300 мс або навіть 1000 мс залежно від налаштувань продуктивності на рівні браузера та ОС. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index c3c528d33..5292ef359 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -202,7 +202,7 @@ for (let key in user) { } ``` -JavaScript бібліотеки також пропонуються функції для зручної масової прив’язки, наприклад [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll) в бібліотеці lodash. +JavaScript бібліотеки також пропонуються функції для зручної масової прив’язки, наприклад [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) в бібліотеці lodash. ```` ## Часткове застосування diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 8b7682b0e..7b93df1fe 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -131,7 +131,6 @@ alert(longEar.jumps); // true (береться з об’єкта rabbit) Хоч це і очевидно, але все ж таки: може бути тільки одна властивість `[[Prototype]]`. Об’єкт не може успадковувати властивості та методи від двох прототипів одночасно. - ```smart header="`__proto__` є старим і давнім getter/setter для `[[Prototype]]`" Вважається поширеною помилкою, особливо для початківців, неможливість чітко визначити різницю між двома поняттями `__proto__` та `[[Prototype]]`. diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index d60ba320d..a941c7181 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -2,7 +2,7 @@ Властивість `"prototype"` широко використовується ядром самого JavaScript. Її використовують всі вбудовані функції конструктора. -Спочатку ми розглянемо деталі, а потім розберемося як використовувати `"prototype"` для додавання нових можливостей вбудованим об’єктам. +Спочатку ми розглянемо деталі, а потім розберемося як додати нових можливостей вбудованим об’єктам. ## Object.prototype diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg deleted file mode 100644 index ede4e1227..000000000 --- a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg +++ /dev/null @@ -1 +0,0 @@ -eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index edf8e1923..99dc2c47b 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -3,15 +3,18 @@ В першій главі цього розділу, ми згадували сучасні методи роботи з прототипом. -Властивість `__proto__` вважається застарілою (підтримується браузером відповідно до стандарту). +Встановлення або читання прототипу за допомогою `obj.__proto__` вважається застарілим і не рекомендуються для використання в майбутньому (перенесено до так званого "Annex B" стандарту JavaScript, призначеного лише для браузерів). -Сучасні методи: +Сучасні методи отримання/встановлення прототипу: -- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- створює пустий об’єкт з властивістю `[[Prototype]]`, що посилається на переданий об’єкт `proto`, та необов’язковими для передачі дескрипторами властивостей `descriptors`. - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- повертає значення `[[Prototype]]` об’єкту `obj`. - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- встановлює значення `[[Prototype]]` об’єкту `obj` рівне `proto`. -Ці методи необхідно використовувати на відміну від `__proto__`. +Єдине використання `__proto__`, яке не викликає негативного ставлення, це як властивість під час створення нового об’єкта: `{ __proto__: ... }`. + +Хоча і для цього є спеціальний метод: + +- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- створює порожній об’єкт із заданим `proto` як `[[Prototype]]` і необов’язковими дескрипторами властивостей. Наприклад: @@ -22,7 +25,7 @@ let animal = { // створюється новий об’єкт з прототипом animal *!* -let rabbit = Object.create(animal); +let rabbit = Object.create(animal); // same as {__proto__: animal} */!* alert(rabbit.eats); // true @@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // змінює прототип об’єк */!* ``` -`Object.create` має необов’язковий другий аргумент: дескриптори властивостей. Ми можемо надати додаткові властивості новому об’єкту, як тут: +`Object.create` має необов’язковий другий аргумент: дескриптори властивостей. + +Ми можемо надати додаткові властивості новому об’єкту, як тут: ```js run let animal = { @@ -57,27 +62,39 @@ alert(rabbit.jumps); // true Ми можемо використати `Object.create`, щоб клонувати об’єкт ефективніше ніж циклом `for..in`: ```js -let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); +let clone = Object.create( + Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) +); ``` Таким чином ми створюємо справжню копію об’єкта `obj`, включаючи всі властивості: перелічувані та не перелічувані, сетери/гетери -- з правильним значенням `[[Prototype]]`. -## Коротка історія -Якщо порахувати всі способи керування властивістю `[[Prototype]]`, їх буде багато! Багато способів робити одне й те ж саме! +## Коротка історія -Чому? +Якщо порахувати всі способи керування властивістю `[[Prototype]]`, їх буде багато! Багато способів робити одне й те ж саме! Як так сталося? Чому? Так склалося історично. -- Властивість `"prototype"` функції-конструктора реалізована з самих давніх часів. -- Пізніше, в 2012 році, метод `Object.create` став стандартом. Це дало можливість створювати об’єкти з певним прототипом, проте не дозволяло отримувати або встановлювати його. Тому браузери реалізували не стандартний аксесор `__proto__`, що дозволяв користувачу отримувати та встановлювати прототип в будь-який час. -- Ще пізніше, в 2015 році, методи `Object.setPrototypeOf` та `Object.getPrototypeOf` були додані до стандарту, для того, щоб виконувати аналогічну функціональність як і `__proto__`. Оскільки `__proto__` було широко реалізовано, воно згадується в Додатку B стандарту як не обов’язкове для не-браузерних середовищ, але вважається свого роду застарілим. +Прототипна спадковість була в мові з самого початку, але способи керування нею розвивалися з часом. + +- Властивість `prototype` функції-конструктора працювала з дуже давніх часів. Це найстаріший спосіб створення об’єктів із заданим прототипом. +- Пізніше, в 2012 році, метод `Object.create` став стандартом. Це дало можливість створювати об’єкти з певним прототипом, проте не дозволяло отримувати або встановлювати його. Тому браузери реалізували не стандартний аксесор `__proto__`, що дозволяв користувачу отримувати та встановлювати прототип в будь-який час, щоб надати розробникам більше гнучкості. +- Ще пізніше, в 2015 році, методи `Object.setPrototypeOf` та `Object.getPrototypeOf` були додані до стандарту, для того, щоб виконувати аналогічну функціональність як і `__proto__`. Оскільки `__proto__` було широко реалізовано, воно згадується в Annex B стандарту як не обов’язкове для не-браузерних середовищ, але вважається свого роду застарілим. +- Пізніше, у 2022 році, було офіційно дозволено використовувати `__proto__` в об'єктних літералах `{...}` (винесено з Annex B), але не як геттер/сеттер `obj.__proto__` (ця можливість все ще в Annex B). + +Чому `__proto__` було замінено функціями `getPrototypeOf/setPrototypeOf`? Таким чином зараз ми маємо всі ці способи для роботи з прототипом. Чому `__proto__` було замінено методами `getPrototypeOf/setPrototypeOf`? Це цікаве питання, яке вимагає від нас розуміння чому `__proto__` має недоліки. Прочитайте далі, щоб дізнатися відповідь. +Чому `__proto__` було частково відновлено і його використання дозволено в `{...}`, але не як геттер/сеттер? + +Це цікаве запитання, яке вимагає від нас розуміння, чому `__proto__` поганий. + +І незабаром ми отримаємо відповідь. + ```warn header="Не змінюйте `[[Prototype]]` в існуючих об’єктів, якщо швидкість важлива" Технічно, ми можемо отримати/встановити `[[Prototype]]` в будь-який момент. Але зазвичай ми тільки встановлюємо його один раз під час створення об’єкту та більше не змінюємо: `rabbit` наслідується від `animal` і це не зміниться. @@ -101,23 +118,34 @@ obj[key] = "певне значення"; alert(obj[key]); // [object Object], не "певне значення"! ``` -В цьому випадку, якщо користувач введе `__proto__`, присвоєння проігнорується! +Тут, якщо користувач вводить `__proto__`, призначення в рядку 4 ігнорується! -Це не повинно дивувати нас. Властивість `__proto__` є особливою: вона має бути або об’єктом або `null`. Рядок не може стати прототипом. +Це, безумовно, може бути дивним для нерозробника, але досить зрозумілим для нас. Властивість `__proto__` є особливою: вона має бути або об’єктом, або `null`. Рядок не може стати прототипом. Ось чому призначення рядка `__proto__` ігнорується. Проте ми не намагалися реалізувати таку поведінку. Ми хотіли зберегти пари ключ/значення і при цьому ключ з назвою `"__proto__"` не зберігся. Тому це помилка! -В цьому прикладі наслідки не такі жахливі. Однак в інших випадках ми можемо встановлювати об’єктні значення і тоді прототип може дійсно змінитись. В результаті, виконання коду піде неправильним та неочікуваним шляхом. +Тут наслідки не такі страшні. Але в інших випадках ми можемо зберігати об’єкти замість рядків у `obj`, і тоді прототип справді буде змінено. У результаті код відпрацює зовсім несподіваним чином. Найгірше в цьому -- зазвичай розробники не задумаються над тим, що така ситуація взагалі можлива. Це робить подібні помилки важкими для виявлення і перетворюються їх у вразливості, особливо коли JavaScript використовується на стороні серверу. -Неочікувані речі можуть траплятися при встановлені значення для властивості `toString`, яка за замовчуванням є функцією, та для інших вбудованих методів. +Неочікувані речі також можуть статися під час призначення `obj.toString`, оскільки це вбудований метод об’єкта. Як ми можемо уникнути цієї проблеми? -В першу чергу, для зберігання ми можемо просто використовувати `Map` замість простих об’єктів, тоді все працює правильно. +В першу чергу, для зберігання ми можемо просто використовувати `Map` замість простих об’єктів, тоді все працює правильно: -Проте `Object` може також послужити нам, оскільки творці мови задумувались над цією проблемою вже дуже давно. +```js run +let map = new Map(); + +let key = prompt("What's the key?", "__proto__"); +map.set(key, "some value"); + +alert(map.get(key)); // "some value" (як і передбачалося) +``` + +...Але синтаксис `Object` часто більш привабливий, оскільки він більш стислий. + +На щастя, ми *можемо* використовувати об’єкти, тому що творці мови давно подумали про цю проблему. `__proto__` не є властивістю об’єкту, але є аксесором `Object.prototype`: @@ -132,6 +160,7 @@ alert(obj[key]); // [object Object], не "певне значення"! ```js run *!* let obj = Object.create(null); +// or: obj = { __proto__: null } */!* let key = prompt("Введіть ключ", "__proto__"); @@ -173,32 +202,26 @@ alert(Object.keys(chineseDictionary)); // hello,bye ## Підсумки -Сучасними методами встановлення та прямого доступу до прототипу є: - -- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- створює пустий об’єкт з переданим `proto` як `[[Prototype]]` (може дорівнювати `null`) та необов’язковими дескрипторами властивостей. -- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- повертає `[[Prototype]]` об’єкту `obj` (так само як і гетер `__proto__`). -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- встановлює `[[Prototype]]` об’єкту `obj` значення `proto` (так само як і сетер `__proto__`). - -Вбудований гетер/сетер `__proto__` небезпечний, якби ми захотіли помістити згенеровані користувачем ключі в об’єкт. Тому що користувач може ввести `"__proto__"` як ключ і буде помилка, з неочікуваними наслідками. +- Щоб створити об'єкт із заданим прототипом, використовуйте: + + - літеральний синтаксис: `{ __proto__: ... }`, дозволяє вказати кілька властивостей + - або [Object.create(proto, [descriptors])](mdn:js/Object/create), дозволяє вказати дескриптори властивостей. -Тому ми можемо використовувати як і `Object.create(null)`, щоб створити "простий" об’єкт без `__proto__`, так і `Map` для цього. + `Object.create` забезпечує простий спосіб поверхневого копіювання об’єкта з усіма дескрипторами: -Також, `Object.create` надає простий спосіб створити копію об’єкту з усіма дескрипторами: + ```js + let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); + ``` -```js -let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); -``` +- Сучасні методи отримання/встановлення прототипу: -Ми також з’ясували що `__proto__` є гетер/сетер для `[[Prototype]]` та існує в `Object.prototype`, як і інші методи. + - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter). + - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter). -Ми можемо створити об’єкт без прототипу за допомогою `Object.create(null)`. Такі об’єкти використовуються як "чисті словники", у них відсутні проблеми з `"__proto__"` як ключем. +- Отримання/встановлення прототипу за допомогою вбудованого `__proto__` геттера/сеттера не рекомендується, тепер це в Annex B специфікації. -Інші методи: +- Ми також розглядали об’єкти без прототипів, створені за допомогою `Object.create(null)` або `{__proto__: null}`. -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- повертає масив перелічуваних власних рядкових властивостей ключі/значення/пари ключ-значення. -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- повертає масив всіх власних символьних ключів. -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- повертає масив всіх власних рядкових ключів. -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- повертає масив усіх власних ключів. -- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): повертає `true` якщо `obj` має власний (не наслідуваний) ключ з назвою `key`. + Ці об’єкти використовуються як сховища ключ-значення для зберігання будь-яких (можливо, створених користувачем) ключів. -Всі методи, що повертають властивості об’єкта (такі як `Object.keys` і інші) -- повертають "власні" властивості. Якщо нам потрібні також наслідувані властивості, ми можемо використовувати цикл `for..in`. + Зазвичай об’єкти успадковують вбудовані методи та `__proto__` геттер/сеттер від `Object.prototype`, роблячи відповідні ключі "зайнятими", і це потенційно викликає побічні ефекти. З прототипом `null` об'єкти справді порожні. diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 51aa4ae2c..8c6f04f5a 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -160,6 +160,7 @@ rabbit.stop(); // Білий Кролик стоїть. Білий Кролик Як зазначалося в розділі , стрілкові функції не мають `super`. Якщо `super` доступний, то він береться із зовнішньої функції. Наприклад: + ```js class Rabbit extends Animal { stop() { @@ -280,8 +281,6 @@ alert(rabbit.earLength); // 10 */!* ``` - - ### Перевизначення поля класу: складна примітка ```warn header="Просунута примітка" @@ -376,7 +375,6 @@ new Rabbit(); // кролик Якщо це стає проблемою, її можна вирішити за допомогою методів або геттерів/сеттерів, а не полів. - ## Super: властивості, [[HomeObject]] ```warn header="Просунута примітка" diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index a38389b71..000d6876f 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -109,6 +109,17 @@ alert( article.title ); // Сьогоднішній дайджест Article.remove({id: 12345}); ``` +````warn header="Статичні методи недоступні для окремих об’єктів" +Статичні методи викликаються для класів, а не для окремих об’єктів. + +Наприклад такий код не працюватиме: + +```js +// ... +article.createTodays(); /// Error: article.createTodays is not a function +``` +```` + ## Статичні властивості [recent browser=Chrome] diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 6f7d43682..8d77dede0 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -103,7 +103,7 @@ new User("Іван").sayHi(); // Привіт, Іван! Це тому, що методи `sayHi` і `sayBye` були спочатку створені в `sayHiMixin`. Таким чином, хоча вони були скопійовані, але їх внутрішня власність `[[HomeObject]]` посилається на `sayHiMixin`, як показано на малюнку вище. -Оскільки `super` шукає батьківські методи в `[[HomeObject]].[[Prototype]]`, то це означає, що воно шукає `sayHiMixin.[[Prototype]]`, а не `User.[[Prototype]]`. +Оскільки `super` шукає батьківські методи в `[[HomeObject]].[[Prototype]]`, то це означає, що воно шукає `sayHiMixin.[[Prototype]]`. ## EventMixin diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index fd16f4f6e..8317858eb 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -632,7 +632,7 @@ window.onerror = function(message, url, line, col, error) { Глобальний обробник `window.onerror` не передбачений для відновлювання роботи скрипту, а тільки відправлення повідомлення про помилку розробникам. -Для логування помилок в таких випадках існують спеціальні вебсервіси: чи . +Для логування помилок в таких випадках існують спеціальні вебсервіси: чи . Вони працюють наступним чином: diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index ca7b66805..f0c82fb5a 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -77,6 +77,9 @@ function loadScript(src, *!*callback*/!*) { } ``` +Подія `onload` описана в статті , +і це спосіб виконати функцію після завантаження та виконання скрипту. + Тепер, якщо ми хочемо викликати нові функції зі скрипту, то повинні написати це у колбеку: ```js @@ -102,7 +105,7 @@ function loadScript(src, callback) { *!* loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { alert(`Cool, the script ${script.src} is loaded`); - alert( _ ); // функція, що оголошена в завантаженому скрипті + alert( _ ); // _ функція, що оголошена в завантаженому скрипті }); */!* ``` diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index dc47e6dc2..da8e9c9a3 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -35,8 +35,8 @@ let promise = new Promise(function(resolve, reject) { В об’єкта `promise`, що повертається конструктором `new Promise` є внутрішні властивості: -- `state` («стан») —- спочатку `"pending"` («очікування»), в результаті виконання функції він може змінюватися на: `"fulfilled"` коли викликається метод `resolve` і на `"rejected"` - коли `reject`. -- `result` (результат) —- спочатку `undefined`, далі змінюється на `value` коли викликається метод `resolve(value)` або `error` коли `reject(error)`. +- `state` (стан) — спочатку `"pending"` (очікування), в результаті виконання функції він може змінюватися на: `"fulfilled"` коли викликається метод `resolve` і на `"rejected"` коли `reject`. +- `result` (результат) — спочатку `undefined`, далі змінюється на `value` коли викликається метод `resolve(value)` або `error` коли `reject(error)`. Отже, виконавець зрештою переводить `promise` в один з наступних станів: @@ -127,9 +127,9 @@ let promise = new Promise(function(resolve, reject) { Властивості `state` and `result` -- внутрішні властивості об’єкта `Promise`, тому ми не маємо до них прямого доступу. Для обробки результату слід використовувати методи: `.then`/`.catch`/`.finally`. Про них далі піде мова. ``` -## Споживачі: then, catch, finally +## Споживачі: then, catch -Об’єкт `Promise` служить зв’язною ланкою між функцією виконавцем (код "виробник" чи "співак") і функціями-споживачами ("фанатами"), котрі отримають або результат, або помилку. Функції споживачі можуть зареєструватись (підписатись) за допомогою методів `.then`, `.catch` і `.finally`. +Об’єкт `Promise` служить зв’язною ланкою між функцією виконавцем (код "виробник" чи "співак") і функціями-споживачами ("фанатами"), котрі отримають або результат, або помилку. Функції споживачі можуть зареєструватись (підписатись) за допомогою методів `.then` та `.catch`. ### then @@ -144,9 +144,9 @@ promise.then( ); ``` -Перший аргумент метода `.then` -- функція що викликається коли проміс успішно виконується, тобто переходить зі стану `"pending"` в `"resolved"`. +Перший аргумент метода `.then` -- функція що викликається коли проміс успішно виконується, тобто переходить зі стану `"pending"` в `"resolved"` і отримує результат. -Другим аргументом метод `.then` приймає функцію що викликається коли проміс переходить в стан `"rejected"`. +Другим аргументом метод `.then` приймає функцію, що викликається коли проміс переходить в стан `"rejected"` і отримує помилку. Для прикладу, наступним чином виглядає реакція на успішно виконаний проміс: @@ -212,15 +212,19 @@ promise.catch(alert); // виведе "Error: Ооооой!" через 1 сек Виклик `.catch(f)` -- це скорочений варіант `.then(null, f)`. -### finally +## Cleanup: finally По аналогії з блоком `finally` зі звичайного `try {...} catch {...}`, у промісів також є свій метод `finally` -Виклик `.finally(func)` подібний до `.then(func, func)`, в тому сенсі, що `func` виконається в будь-якому випадку, коли проміс перейде в стан `"виконано (settled)"` не залежно від того став він `resolved` чи `rejected`. +Виклик `.finally(f)` подібний до `.then(f, f)`, в тому сенсі, що `f` виконається в будь-якому випадку, коли проміс перейде в стан `"виконано (settled)"` не залежно від того став він `resolved` чи `rejected`. `finally` добре підходить для чистки, наприклад зупинки індикатора завантаження, тому, що його потрібно зупинити незалежно від результату. -Наприклад: +Наприклад зупинка завантаження індикаторів, закриття непотрібних підключень тощо. + +Подумайте про це як про завершення вечірки. Незалежно від того, була вечірка хорошою чи поганою, скільки друзів на ній було, ми все одно повинні (або принаймні повинні) зробити прибрати після неї. + +Код може виглядати так: ```js new Promise((resolve, reject) => { @@ -234,9 +238,13 @@ new Promise((resolve, reject) => { .then(result => вивести результат, err => вивести помилку) ``` -Проте `finally(f)` точно не є псевдонімом `then(f,f)`. Є декілька незначних відмінностей: +Проте `finally(f)` точно не є псевдонімом `then(f,f)`. + +Є декілька незначних відмінностей: 1. Обробник `finally` не приймає аргументів. В `finally` ми не знаємо як був завершений проміс, успішно чи ні. І це нормально, тому що зазвичай наше завдання заключаєтсья в тому щоб виконати "загальні" процедури доопрацювання. + + Подивіться на наведений вище приклад: як бачите, обробник `finally` не має аргументів, а результат промісу обробляється наступним обробником. 2. Обробник `finally` пропускає результат чи помилку до наступних обробників. Наприклад, тут результат проходить через `finally` до `then`: @@ -244,27 +252,42 @@ new Promise((resolve, reject) => { new Promise((resolve, reject) => { setTimeout(() => resolve("результат"), 2000) }) - .finally(() => alert("Проміс завершений")) - .then(result => alert(result)); // <-- .then обробляє резульат + .finally(() => alert("Проміс завершений")) // запускається першим + .then(result => alert(result)); // <-- .then показує "резульат" ``` - А тут помилка з проміса проходить через `finally` до `catch`: + Як бачите, "результат", який повертає перший проміс, передається через `finally` наступному `then`. + + Це дуже зручно, оскільки `finally` не призначено для обробки результату промісу. Як було сказано, це місце для загального очищення, незалежно від результату. + + А ось приклад помилки, щоб ми могли побачити, як вона передається через `finally` до `catch`: ```js run new Promise((resolve, reject) => { throw new Error("помилка"); }) - .finally(() => alert("Проміс завершений")) + .finally(() => alert("Проміс завершений")) // запускається першим .catch(err => alert(err)); // <-- .catch обробляє об’єкт помилки ``` -Це дуже зручно, оскільки `finally` не призначений для обробки результату промісу. Так що він буде пропускати його через себе далі. +3. Обробник `finally` також не повинен нічого повертати. Якщо все ж таки він щось повертає, це значення ігнорується. + + Єдиним винятком із цього правила є випадки, коли обробник `finally` видає помилку. Бл потім ця помилка переходить до наступного обробника замість будь-якого попереднього результату проміса. -Ми поговоримо більш детально про створення ланцюжка промісів і передачі результатів між обробниками в наступному розділі. +To summarize: +- A `finally` handler doesn't get the outcome of the previous handler (it has no arguments). This outcome is passed through instead, to the next suitable handler. +- If a `finally` handler returns something, it's ignored. +- When `finally` throws an error, then the execution goes to the nearest error handler. + +These features are helpful and make things work just the right way if we use `finally` how it's supposed to be used: for generic cleanup procedures. ````smart header="На завершених промісах обробники запускаються одразу" -Якщо проміс в стані очікування, `.then/catch/finally` будуть на нього чекати. Проте, якщо проміс вже завершений, то обробник виконається одразу ж: +Якщо проміс в стані очікування, `.then/catch/finally` будуть на нього чекати. + +Іноді може бути так, що проміс уже виконано, коли ми додаємо до нього обробник. + +У такому випадку ці обробники просто запускаються негайно: ```js run // при створенні проміс одразу ж перейде в стан успішно завершений (`"resolved"`) @@ -278,10 +301,10 @@ promise.then(alert); // виведе "завершено!" Проміси своєю чергою більш гнучкі. Ми можемо додати обробник в будь-який час: якщо результат вже є, вони просто виконуються. ```` -Тепер, розгляньмо декілька прикладів з тим як проміси можуть облегшити нам написання асинхронного коду. - ## Приклад: loadScript [#loadscript] +Тепер, розгляньмо декілька прикладів з тим як проміси можуть облегшити нам написання асинхронного коду. + В нас є функція `loadScript` для завантаження скрипта з попереднього розділу. Згадаймо як виглядає варіант з колбеком: diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md index fd31f5017..4952d1b20 100644 --- a/1-js/11-async/03-promise-chaining/article.md +++ b/1-js/11-async/03-promise-chaining/article.md @@ -72,7 +72,7 @@ promise.then(function(result) { }); ``` -Те, що ми зробили тут, — це додали лише кілька обробників до одного промісу. Вони не передають результат один одному; натомість вони обробляють його самостійно. +Те, що ми зробили тут -- це додали лише кілька обробників до одного промісу. Вони не передають результат один одному; натомість вони обробляють його самостійно. Ось малюнок (порівняйте його з ланцюжком вище): @@ -224,7 +224,7 @@ JavaScript перевіряє об’єкт, повернутий обробни ## Складніший приклад: fetch -У інтерфейсному програмуванні проміси часто використовуються для мережевих запитів. Тож давайте подивимося на розширений приклад цього. +У фронтенд розробці проміси часто використовуються для мережевих запитів. Тож давайте подивимося на розширений приклад цього. Ми будемо використовувати метод [fetch](info:fetch), щоб завантажити інформацію про користувача з віддаленого сервера. Він має багато опціональних параметрів, які розглядаються в [окремих розділах](info:fetch), але основний синтаксис досить простий: diff --git a/1-js/11-async/04-promise-error-handling/article.md b/1-js/11-async/04-promise-error-handling/article.md index 492aea0c8..0b3f0e1bf 100644 --- a/1-js/11-async/04-promise-error-handling/article.md +++ b/1-js/11-async/04-promise-error-handling/article.md @@ -199,6 +199,7 @@ new Promise(function() { ## Підсумки - `.catch` перехоплює усі види помилок в промісах: будь то виклик `reject()` або помилка, кинута в обробнику за допомогою `throw`. +- `.then` так само виловлює помилки, якщо надати другий аргумент (який є обробником помилок). - Необхідно розміщувати `.catch` там, де ми хочемо обробити помилки і знаємо, як це зробити. Обробник може проаналізувати помилку (можуть бути корисні призначені для користувача класи помилок) і прокинути її, якщо нічого не знає про неї (можливо, це програмна помилка). - Можна і зовсім не використовувати `.catch`, якщо немає нормального способу відновитися після помилки. - У будь-якому випадку нам слід використовувати обробник події `unhandledrejection` (для браузерів і аналог для іншого оточення), щоб відстежувати необроблені помилки і інформувати про них користувача (і, можливо, наш сервер), завдяки чому наш застосунок ніколи не буде "просто помирати". diff --git a/1-js/11-async/06-promisify/article.md b/1-js/11-async/06-promisify/article.md index d7b937398..60c9287de 100644 --- a/1-js/11-async/06-promisify/article.md +++ b/1-js/11-async/06-promisify/article.md @@ -124,7 +124,7 @@ f(...).then(arrayOfResults => ..., err => ...); Існують також модулі з більш гнучкою промісифікацією, наприклад, [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify) або вбудована функція `util.promisify` в Node.js. ```smart -Промісифікація –- це чудовий підхід, особливо якщо ви будете використовувати `async/await` (дивіться наступний розділ), але вона не є повноцінно заміною будь-яких колбеків. +Промісифікація –- це чудовий підхід, особливо якщо ви будете використовувати `async/await` (розглянемо пізніше в розділі ), але вона не є повноцінно заміною будь-яких колбеків. Пам'ятайте, проміс може мати лише один результат, але колбек технічно може викликатися скільки завгодно разів. diff --git a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md index ef6100857..255effd6f 100644 --- a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md +++ b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md @@ -3,7 +3,7 @@ function* pseudoRandom(seed) { let value = seed; while(true) { - value = value * 16807 % 2147483647 + value = value * 16807 % 2147483647; yield value; } diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index c6f4ca0db..a56a22602 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -9,7 +9,7 @@ Наприклад: -- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- одна з найстаріших модульних систем, спочатку реалізована бібліотекою [require.js](http://requirejs.org/). +- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- одна з найстаріших модульних систем, спочатку реалізована бібліотекою [require.js](https://requirejs.org/). - [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- модульна система, створена для сервера Node.js. - [UMD](https://github.com/umdjs/umd) -- ще одна модульна система, пропонується як універсальна, сумісна з AMD і CommonJS. @@ -272,7 +272,7 @@ sayHi(); // Ready to serve, *!*Pete*/!*! - модулі, навіть якщо завантажилися швидко, очікують на повне завантаження HTML документа, і тільки потім виконуються. - зберігається відносний порядок скриптів: скрипти, що йдуть раніше у документі, виконуються раніше. -Як побічний ефект, модулі завжди бачать повністю завантажену HTML-сторінку, включаючи елементи під ними. +Як побічний ефект, модулі завжди бачать повністю завантажену HTML-сторінку, включаючи HTML-елементи під ними. Наприклад: diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 4ccc0ef53..ab82882b6 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -93,25 +93,14 @@ say.sayBye('Іван'); На це є декілька причин. -1. Сучасні інструменти збирання ([webpack](https://webpack.js.org/) та інші) об’єднують модулі разом, оптимізують їх для пришвидшення завантаження та видаляють невикористані частини. +1. Явний список того, що потрібно імпортувати дає коротші імена: `sayHi()` замість `say.sayHi()`. +2. Явний список того, що потрібно імпортувати дає краще розуміння структури коду: що використано та в якому місці. Також дозволяє підтримувати та рефакторити код легше. - Скажімо, ми додали сторонню бібліотеку, з багатьма функціями, `say.js` до нашого проекту: - ```js - // 📁 say.js - export function sayHi() { ... } - export function sayBye() { ... } - export function becomeSilent() { ... } - ``` - - Припустимо, ми використовуємо тільки одну функцію з `say.js` в нашому проекті: - ```js - // 📁 main.js - import {sayHi} from './say.js'; - ``` - ...Тоді оптимізатор побачить це та видалить інші функції із об’єднаного коду, що робить зібраний проект меншим. Так званий "tree-shaking". +```smart header="Не бійтеся імпортувати занадто багато" +Сучасні інструменти збірки, такі як [webpack](https://webpack.js.org/) та інші, об’єднують модулі разом і оптимізують їх для прискорення завантаження. Вони також видаляють імпорти, що не використовуються. -2. Явний список того, що потрібно імпортувати дає коротші імена: `sayHi()` замість `say.sayHi()`. -3. Явний список того, що потрібно імпортувати дає краще розуміння структури коду: що використано та в якому місці. Також дозволяє підтримувати та рефакторити код легше. +Наприклад, якщо ви `import * as library` з величезної бібліотеки коду, а потім використаєте лише кілька методів, тоді невикористані методи [не будуть включені](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) у оптимізований бандл. +``` ## Імпорт "as" diff --git a/1-js/99-js-misc/01-proxy/proxy.svg b/1-js/99-js-misc/01-proxy/proxy.svg index 157e350f4..6b2224cfd 100644 --- a/1-js/99-js-misc/01-proxy/proxy.svg +++ b/1-js/99-js-misc/01-proxy/proxy.svg @@ -1 +1 @@ -test: 5proxytargetget proxy.test5 \ No newline at end of file +test: 5proxytargetget proxy.test5 \ No newline at end of file diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md index 231746ff6..1c0041267 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -87,7 +87,7 @@ hi(); // Помилка, тому що this -- це undefined (user, "hi", true) ``` -Коли дужки `()` викликаються з посилальним типом, вони отримують повну інформацію про об’єкт та його метод, і можуть встановити правильний `this` (` =user` у даному випадку). +Коли дужки `()` викликаються з посилальним типом, вони отримують повну інформацію про об’єкт та його метод, і можуть встановити правильний `this` (`user` у даному випадку). Посилальний тип -- це особливий "посередницький" внутрішній тип, який використовується з метою передачі інформації від крапки `.` до дужок виклику `()`. diff --git a/1-js/99-js-misc/06-unicode/article.md b/1-js/99-js-misc/06-unicode/article.md new file mode 100644 index 000000000..4f144f824 --- /dev/null +++ b/1-js/99-js-misc/06-unicode/article.md @@ -0,0 +1,172 @@ + +# Unicode, String internals + +```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. +``` + +As we already know, JavaScript strings are based on [Unicode](https://en.wikipedia.org/wiki/Unicode): each character is represented by a byte sequence of 1-4 bytes. + +JavaScript allows us to insert a character into a string by specifying its hexadecimal Unicode code with one of these three notations: + +- `\xXX` + + `XX` must be two hexadecimal digits with a value between `00` and `FF`, then `\xXX` is the character whose Unicode code is `XX`. + + Because the `\xXX` notation supports only two hexadecimal digits, it can be used only for the first 256 Unicode characters. + + These first 256 characters include the Latin alphabet, most basic syntax characters, and some others. For example, `"\x7A"` is the same as `"z"` (Unicode `U+007A`). + + ```js run + alert( "\x7A" ); // z + alert( "\xA9" ); // ©, the copyright symbol + ``` + +- `\uXXXX` + `XXXX` must be exactly 4 hex digits with the value between `0000` and `FFFF`, then `\uXXXX` is the character whose Unicode code is `XXXX`. + + Characters with Unicode values greater than `U+FFFF` can also be represented with this notation, but in this case, we will need to use a so called surrogate pair (we will talk about surrogate pairs later in this chapter). + + ```js run + alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation + alert( "\u044F" ); // я, the Cyrillic alphabet letter + alert( "\u2191" ); // ↑, the arrow up symbol + ``` + +- `\u{X…XXXXXX}` + + `X…XXXXXX` must be a hexadecimal value of 1 to 6 bytes between `0` and `10FFFF` (the highest code point defined by Unicode). This notation allows us to easily represent all existing Unicode characters. + + ```js run + alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode) + alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) + ``` + +## Surrogate pairs + +All frequently used characters have 2-byte codes (4 hex digits). Letters in most European languages, numbers, and the basic unified CJK ideographic sets (CJK -- from Chinese, Japanese, and Korean writing systems), have a 2-byte representation. + +Initially, JavaScript was based on UTF-16 encoding that only allowed 2 bytes per character. But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol of Unicode. + +So rare symbols that require more than 2 bytes are encoded with a pair of 2-byte characters called "a surrogate pair". + +As a side effect, 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 character +``` + +That's because 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` property shows a length of `2`. + +Getting a symbol can also be tricky, because most language features treat surrogate pairs as two characters. + +For example, here we can see two odd characters in the output: + +```js run +alert( '𝒳'[0] ); // shows strange symbols... +alert( '𝒳'[1] ); // ...pieces of the surrogate pair +``` + +Pieces of a 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. + +So the methods [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) and [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) were added in JavaScript to deal with surrogate pairs. + +They are essentially the same as [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt), but they treat surrogate pairs correctly. + +One can see the difference here: + +```js run +// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of 𝒳: + +alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 + +// codePointAt is surrogate-pair aware +alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair +``` + +That said, if we take from position 1 (and that's rather incorrect here), then they both return only the 2nd part of the pair: + +```js run +alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 +alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 +// meaningless 2nd half of the pair +``` + +You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. + +````warn header="Takeaway: splitting strings at an arbitrary point is dangerous" +We can't just split a string at an arbitrary position, e.g. take `str.slice(0, 4)` and expect it to be a valid string, e.g.: + +```js run +alert( 'hi 😂'.slice(0, 4) ); // hi [?] +``` + +Here we can see a garbage character (first half of the smile surrogate pair) in the output. + +Just be aware of it if you intend to reliably work with surrogate pairs. May not be a big problem, but at least you should understand what happens. +```` + +## Diacritical marks and normalization + +In many languages, there are symbols that are composed of the base character with a mark above/under it. + +For instance, the letter `a` can be the base character for these characters: `àáâäãåā`. + +Most common "composite" characters have their own code in the Unicode table. But not all of them, because there are too many possible combinations. + +To support arbitrary compositions, the Unicode standard allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it. + +For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. + +```js run +alert( 'S\u0307' ); // Ṡ +``` + +If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. + +For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. + +For example: + +```js run +alert( 'S\u0307\u0323' ); // Ṩ +``` + +This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. + +For instance: + +```js run +let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below +let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above + +alert( `s1: ${s1}, s2: ${s2}` ); + +alert( s1 == s2 ); // false though the characters look identical (?!) +``` + +To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. + +It is implemented by [str.normalize()](mdn:js/String/normalize). + +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` + +It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +In reality, this is not always the case. The reason is that the symbol `Ṩ` is "common enough", so Unicode creators included it in the main table and gave it the code. + +If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index 2dfe8bad6..48cce1cc9 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -4,7 +4,7 @@ Платформою може бути браузер, або вебсервер або інший *хост*, навіть "розумна" кавоварка, якщо вона може запустити JavaScript. Кожна з них забезпечує специфічну для платформи функціональність. Специфікація JavaScript називає це *середовищем*. -Середовище надає власні об’єкти та додаткові функції до базової мови. Веббраузери дають засоби для керування вебсторінками. Node.js забезпечує функції сервера і так далі. +Середовище надає власні об’єкти та додаткові функції до базової мови. Веббраузери дають засоби для керування вебсторінками. Node.js забезпечує функції сервера тощо. На рисунку нижче показано в загальних рисах те, що ми маємо, коли JavaScript працює в веббраузері: @@ -32,7 +32,7 @@ window.sayHi(); alert(window.innerHeight); // внутрішня висота вікна ``` -Існує набагато більше методів та властивостей вікна, ми їх розглянемо пізніше. +Існує набагато більше методів та властивостей об'єкта window, ми їх розглянемо пізніше. ## DOM (Document Object Model) @@ -106,7 +106,7 @@ BOM -- це частина загальної [HTML-специфікації](ht Буль ласка, збережіть ці посилання, оскільки у них подано багато цікавої інформації для вивчення, яку неможливо цілком розглянути і все запам’ятати. -Коли ви хочете прочитати про властивість або метод, керівництво Mozilla на також є хорошим ресурсом, але відповідна специфікація може бути кращим ресурсом: вона складніша і її довше читати, але вона зробить ваші знання фундаментальними та повними. +Коли ви хочете прочитати про властивість або метод, посібник Mozilla за адресою також є хорошим ресурсом, але відповідна специфікація може бути навіть кращим ресурсом: вона складніша і її довше читати, але вона зробить ваші знання фундаментальними та повними. Щоб щось знайти в Інтернеті, часто зручно використовувати пошук в форматі "WHATWG [запит]" або "MDN [запит]", наприклад, , . diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 8296edca6..842840850 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -212,7 +212,7 @@ drawHtmlTree(node6, 'div.domtree', 690, 500); ## Поекспериментуйте самі -Щоб побачити структуру DOM у режимі реального часу, спробуйте [Live Dom Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Просто введіть щось, і внизу ви відразу побачите, як змінюється DOM. +Щоб побачити структуру DOM у режимі реального часу, спробуйте [Live Dom Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Просто введіть щось, і внизу ви відразу побачите, як змінюється DOM. Іншим способом вивчення DOM є використання інструментів розробника браузера. Взагалі, це те, що ми використовуємо при розробці кожен день. diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.svg b/2-ui/1-document/02-dom-nodes/domconsole0.svg index ea0d9141c..eb99f193f 100644 --- a/2-ui/1-document/02-dom-nodes/domconsole0.svg +++ b/2-ui/1-document/02-dom-nodes/domconsole0.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.svg b/2-ui/1-document/02-dom-nodes/domconsole1.svg index d7f32debb..02ef5f0a6 100644 --- a/2-ui/1-document/02-dom-nodes/domconsole1.svg +++ b/2-ui/1-document/02-dom-nodes/domconsole1.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/elk.svg b/2-ui/1-document/02-dom-nodes/elk.svg index 1797a099f..448eea9d1 100644 --- a/2-ui/1-document/02-dom-nodes/elk.svg +++ b/2-ui/1-document/02-dom-nodes/elk.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/inspect.svg b/2-ui/1-document/02-dom-nodes/inspect.svg index a894a5c0e..60696ec0d 100644 --- a/2-ui/1-document/02-dom-nodes/inspect.svg +++ b/2-ui/1-document/02-dom-nodes/inspect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index eb111bc00..ae6a8c6f3 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -55,7 +55,7 @@ ``` ```warn header="Будь ласка, не використовуйте id-іменовані глобальні змінні для доступу до елементів" -Ця поведінка описана [у специфікації](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), тож це свого роду стандарт. Але він підтримується в основному для сумісності. +Ця поведінка описана [у специфікації](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), тож це свого роду стандарт. Але він підтримується в основному для сумісності. Браузер намагається нам допомогти, змішуючи простори імен JS і DOM. Це зручно для простих скриптів, які знаходяться прямо в HTML, але загалом це не дуже добре. Можуть виникнути конфлікти імен. Крім того, коли хтось читає JS-код і не бачить HTML, незрозуміло, звідки приходить змінна. @@ -116,7 +116,7 @@ Попередні методи виконували пошук по DOM. -Метод [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) нічого не шукає, він просто перевіряє, чи відповідає `elem` заданому CSS-селектору. Він повертає `true` або `false`. +Метод [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) нічого не шукає, він просто перевіряє, чи відповідає `elem` заданому CSS-селектору. Він повертає `true` або `false`. Цей метод стає в нагоді, коли ми перебираємо елементи (наприклад, у масиві чи чомусь подібному) і намагаємося відфільтрувати ті, які нас цікавлять. diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index a3c1900e9..d75a9d28b 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -18,7 +18,35 @@ Класи: -- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) - кореневий "абстрактний" клас для всього. +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- це кореневий "абстрактний" клас клас для всього. + + Об’єкти цього класу ніколи не створюються. Він служить основою, тому всі вузли DOM підтримують так звані "події", які ми розглянемо пізніше. + +- [Node](https://dom.spec.whatwg.org/#interface-node) -- це також "абстрактний" клас, що служить базою для вузлів DOM. + + Він забезпечує основну функціональність дерева: `parentNode`, `nextSibling`, `childNodes` і так далі (це гетери). Об’єкти класу `Node` ніколи не створюються. Але є конкретні класи вузлів, які успадковуються від нього, і таким чином успадковують функціональність `Node`. + +- [Document](https://dom.spec.whatwg.org/#interface-document), з історичних причин часто успадковується `HTMLDocument` (хоча остання специфікація цього не диктує) -- це документ як ціле. + + Глобальний об’єкт `document` належить саме цьому класу. Він служить точкою входу в DOM. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- "абстрактний" клас, успадкований: + - [Text](https://dom.spec.whatwg.org/#interface-text) -- клас, що відповідає тексту всередині елементів, напр. `Hello` в `

Hello

`. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- клас для коментарів. Вони не відображаються, але кожен коментар стає членом DOM. + +- [Element](https://dom.spec.whatwg.org/#interface-element) -- це базовий клас для елементів DOM. + + Він забезпечує навігацію на рівні елементів, таку як `nextElementSibling`, `children` та пошукові методи, такі як `getElementsByTagName`, `querySelector`. + + Браузер підтримує не тільки HTML, але й XML та SVG. Клас `Element` служить базою для більш специфічних класів: `SVGElement`, `XMLElement` (вони нам тут не потрібні) та `HTMLElement`. + +- Нарешті, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- це, основний клас для всіх елементів HTML. Ми будемо працювати з ним більшу частину часу. + + Він успадковується конкретними елементами HTML: + - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- це клас для `` елементів, + - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- це клас для `` елементів, + - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- це клас для `` елементів, + - ...тощо. Об'єкти цього класу ніколи не створюються. Він служить базою, щоб всі вузли DOM підтримували так звані "події", які ми розглянемо пізніше. diff --git a/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy.svg b/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy.svg index d96a631fe..ccd93d500 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy.svg +++ b/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy.svg @@ -1 +1 @@ -EventTargetNode Element HTMLElement HTMLBodyElement HTMLInputElement HTMLAnchorElement Document HTMLDocument CharacterData Весь документ в цілому<input type="…"><body><a href="…">< div > ... </ div >Comment <!--коментар-->Text "Привіт" \ No newline at end of file +EventTargetNode Element HTMLElement HTMLBodyElement HTMLInputElement HTMLAnchorElement Document HTMLDocument CharacterData Document as a whole<input type="…"><body><a href="…">< div > ... </ div >Comment <!--comment-->Text "Hello" \ No newline at end of file diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md index 245e1a846..b9b5d9a42 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md @@ -22,6 +22,6 @@ importance: 1 alert(table); // таблиця, як і має бути table.remove(); - // чому текст "ааа" залишився в документі? + // чому текст "aaa" залишився в документі? ``` diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index 2bd3ab9c4..79966ecfb 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -128,6 +128,14 @@ setTimeout(() => document.body.style.display = "", 1000); // назад до н Якщо ми встановлюємо порожній рядок значенням властивості `style.display`, то браузер звичайним чином застосовує CSS-класи і його вбудовані стилі, так, наче тут взагалі не було такої властивості `style.display`. +Також для цього є спеціальний метод, `elem.style.removeProperty('style property')`. Отже, ми можемо видалити таку властивість: + +```js run +document.body.style.background = 'red'; // ставить колір red до background + +setTimeout(() => document.body.style.removeProperty('background'), 1000); // видаляє background через 1 секунду +``` + ````smart header="Повний перезапис за допомогою `style.cssText`" Зазвичай, `style.*` використовується для встановлення окремих властивостей стилю. Немає можливості задати весь стиль, як от `div.style="color: red; width: 100px"`, оскільки `div.style` -- це об’єкт, і він придатний лише для читання. @@ -261,20 +269,6 @@ pseudo Слід завжди запитувати точну назву властивості, значення якої потрібно отримати, як `paddingLeft`, `marginTop` чи `borderTopWidth`. Інакше коректний результат не гарантовано. Наприклад, якщо на елементі задано властивості `paddingLeft/paddingTop`, що ми отримаємо, запитавши значення `getComputedStyle(elem).padding`? Нічого, чи може якесь "згенероване" значення наявних полів? Тут немає жодного стандартного правила. - -Також існують інші непослідовності. Для прикладу, деякі браузери (Chrome) покажуть `10px` в документі, наведеному нижче, а інші (Firefox) -- цього не зроблять: - -```html run - - -``` ```` ```smart header="Стилі, які застосовані на `:visited` (відвідані) посилання -- приховуються!" diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg index 4ae90b1c7..f5bd9f4f9 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.svg b/2-ui/1-document/09-size-and-scroll/metric-all.svg index a5dadb47f..20a59e18d 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-all.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-all.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg index 83864b4c5..2603b05fb 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-css.svg b/2-ui/1-document/09-size-and-scroll/metric-css.svg index 13aa62afd..1f2e5f780 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-css.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-css.svg @@ -1 +1 @@ -padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg index 9e247639b..2d108473e 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg @@ -1 +1 @@ -offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file +offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg index 49bdccda7..4d30d90cc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg index c6d14d0f3..7f72de422 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg index 0c3d29952..75a24e3bc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg index 829d27ee8..18cd37a74 100644 --- a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg +++ b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg @@ -1 +1 @@ -documentElement.clientHeightdocumentElement.clientWidth \ No newline at end of file +documentElement.clientHeightdocumentElement.clientWidth \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index 795b26b2c..fca00d4dd 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -36,7 +36,7 @@ ```online Наприклад, натисніть на цю кнопку, щоб побачити ЇЇ координати відносно вікна: -

+

diff --git a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html index 253fb0531..10ffd72ef 100644 --- a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html +++ b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html @@ -9,8 +9,8 @@ [20] readyState:interactive [21] DOMContentLoaded [30] iframe onload - [40] readyState:complete [40] img onload + [40] readyState:complete [40] window onload --> diff --git a/2-ui/99-ui-misc/02-selection-range/article.md b/2-ui/99-ui-misc/02-selection-range/article.md index c5ab3fbdd..819bcba29 100644 --- a/2-ui/99-ui-misc/02-selection-range/article.md +++ b/2-ui/99-ui-misc/02-selection-range/article.md @@ -217,7 +217,7 @@ The range object that we created in the example above has following properties: ## Range selection methods -There are many convenience methods to manipulate ranges. +There are many convenient methods to manipulate ranges. We've already seen `setStart` and `setEnd`, here are other similar methods. @@ -408,7 +408,7 @@ From – To There are two approaches to copying the selected content: 1. We can use `document.getSelection().toString()` to get it as text. -2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangesAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. +2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangeAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. Here's the demo of copying the selected content both as text and as DOM nodes: @@ -438,7 +438,7 @@ As text: ## Selection methods -We can work with the selection by addding/removing ranges: +We can work with the selection by adding/removing ranges: - `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. - `addRange(range)` -- add `range` to selection. All browsers except Firefox ignore the call, if the selection already has an associated range. diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg index 9ebcffaac..a97d1b47a 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg index 088c71c20..2a8f9aca3 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg index f13c6d74a..32843436d 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg @@ -1 +1 @@ -startContainer (<p>.firstChild)startOffset (=2)commonAncestorContainer (<p>)endContainer (<b>.firstChild)endOffset (=3) \ No newline at end of file +startContainer (<p>.firstChild)startOffset (=2)commonAncestorContainer (<p>)endContainer (<b>.firstChild)endOffset (=3) \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg index 4bf5b00b0..859f755ce 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg b/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg index 6399f9d5e..85615d38f 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg @@ -1 +1 @@ -focusanchormouse move direction \ No newline at end of file +focusanchormouse move direction \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg b/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg index 03c6fc5c6..511b00a26 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg @@ -1 +1 @@ -anchorfocusmouse move direction \ No newline at end of file +anchorfocusmouse move direction \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg b/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg index 050852d3d..aa7ff1eb7 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg @@ -1 +1 @@ -selection \ No newline at end of file +selection \ No newline at end of file diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index 06205ca12..3ee2b687f 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -38,26 +38,6 @@ button.onclick = () => { Таким чином користувачі дещо захищені від небажаних спливаючих вікон, але функціональність не відключається повністю. -Що робити, якщо спливаюче вікно відкривається з `onclick`, але після `setTimeout`? Це трохи складніше. - -Спробуйте цей код: - -```js run -// відкривається через 3 секунди -setTimeout(() => window.open('http://google.com'), 3000); -``` - -Спливаюче вікно відкривається в Chrome, але блокується у Firefox. - -...Якщо ми зменшимо затримку, спливаюче вікно працюватиме і у Firefox: - -```js run -// відкривається через 1 секунду -setTimeout(() => window.open('http://google.com'), 1000); -``` - -Різниця полягає в тому, що Firefox допускає тайм-аут у 2000 мс або менше, але все, що понад -- не викликає його довіри, так як передбачається, що у разі відкриття вікна все відбувається без відома користувача. Саме тому спливаюче вікно із першого прикладу буде заблоковано, а з другого -- ні. - ## window.open Синтаксис відкриття спливаючого вікна: `window.open(url, name, params)`: diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index 5ef1ce835..211deb641 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -238,7 +238,7 @@ const stream = readableStream.getReader(); while (true) { // для кожної ітерації: дані є наступним фрагментом(частиною) blob - let { done, data } = await stream.read(); + let { done, value } = await stream.read(); if (done) { // більше немає даних у потоці console.log('all blob processed.'); @@ -246,7 +246,7 @@ while (true) { } // зробити щось із частиною даних, яку ми щойно прочитали з blob - console.log(data); + console.log(value); } ``` diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index 6ecd3013b..94c995059 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -169,6 +169,7 @@ Access-Control-Allow-Origin: https://javascript.info - `Cache-Control` - `Content-Language` +- `Content-Length` - `Content-Type` - `Expires` - `Last-Modified` @@ -176,12 +177,6 @@ Access-Control-Allow-Origin: https://javascript.info Доступ до будь-якого іншого заголовка відповіді викликає помилку. -```smart -У списку немає заголовка `Content-Length`! - -Цей заголовок містить повну довжину відповіді. Отже, якщо ми щось завантажуємо й хочемо відстежувати прогрес у відсотках, тоді для доступу до цього заголовка потрібен додатковий дозвіл (дивись нижче). -``` - Щоб надати JavaScript доступ до будь-якого іншого заголовка відповіді, сервер повинен надіслати заголовок `Access-Control-Expose-Headers`. Він містить розділений комами список небезпечних імен заголовків, які мають бути доступними. Наприклад: @@ -190,14 +185,15 @@ Access-Control-Allow-Origin: https://javascript.info 200 OK Content-Type:text/html; charset=UTF-8 Content-Length: 12345 +Content-Encoding: gzip API-Key: 2c9de507f2c54aa1 Access-Control-Allow-Origin: https://javascript.info *!* -Access-Control-Expose-Headers: Content-Length,API-Key +Access-Control-Expose-Headers: Content-Encoding,API-Key */!* ``` -З таким заголовком, як `Access-Control-Expose-Headers`, скрипту дозволено читати заголовки `Content-Length` і `API-Key` відповідей. +З таким заголовком, як `Access-Control-Expose-Headers`, скрипту дозволено читати заголовки `Content-Encoding` і `API-Key` відповідей. ## "Небезпечні" запити diff --git a/5-network/06-fetch-api/article.md b/5-network/06-fetch-api/article.md index 3f8a57eeb..e6a95eca0 100644 --- a/5-network/06-fetch-api/article.md +++ b/5-network/06-fetch-api/article.md @@ -24,7 +24,7 @@ let promise = fetch(url, { body: undefined // string, FormData, Blob, BufferSource або URLSearchParams referrer: "about:client", // або "", щоб не посилати заголовок Referer, // або URL з поточного джерела - referrerPolicy: "no-referrer-when-downgrade", // no-referrer, origin, same-origin... + referrerPolicy: "strict-origin-when-cross-origin", // no-referrer-when-downgrade, no-referrer, origin, same-origin... mode: "cors", // same-origin, no-cors credentials: "same-origin", // omit, include cache: "default", // no-store, reload, no-cache, force-cache або only-if-cached @@ -85,27 +85,27 @@ fetch('/page', { Можливі значення описані у [Referrer Policy specification](https://w3c.github.io/webappsec-referrer-policy/): +- **`"strict-origin-when-cross-origin"`** -- для запитів до однакового джерела надсилати повний `Referer`, для запитів між різними джерелами надсилати лише джерело, якщо це не HTTPS→HTTP запит, тоді нічого не надсилати. - **`"no-referrer-when-downgrade"`** -- типове значення: повний `Referer` надсилається завжди, хіба що ми не надсилаємо запит із HTTPS на HTTP (на менш безпечний протокол). - **`"no-referrer"`** -- ніколи не надсилати `Referer`. - **`"origin"`** -- надсилати лише джерело в `Referer`, а не повну URL-адресу сторінки, наприклад лише `http://site.com` замість `http://site.com/path`. - **`"origin-when-cross-origin"`** -- надсилати повний `Referer` для запитів до однакового джерела, але лише частину джерела для запитів між різними джерелами (як показано вище). - **`"same-origin"`** -- надсилати повний `Referer` для запитів до однакового джерела, але без `Referer` для запитів між різними джерелами. - **`"strict-origin"`** -- надсилати лише джерело, а не `Referer` для HTTPS→HTTP запитів. -- **`"strict-origin-when-cross-origin"`** -- для запитів до однакового джерела надсилати повний `Referer`, для запитів між різними джерелами надсилати лише джерело, якщо це не HTTPS→HTTP запит, тоді нічого не надсилати. - **`"unsafe-url"`** -- завжди надсилати повну URL-адресу в `Referer`, навіть для HTTPS→HTTP запитів. Ось таблиця зі всіма комбінаціями: -| Значення | До того ж джерела | До іншого джерела | HTTPS→HTTP | -|------------------------------------------------------------|-------------------|-------------------|------------| -| `"no-referrer"` | - | - | - | -| `"no-referrer-when-downgrade"` або `""` (типове значення) | full | full | - | -| `"origin"` | origin | origin | origin | -| `"origin-when-cross-origin"` | full | origin | origin | -| `"same-origin"` | full | - | - | -| `"strict-origin"` | origin | origin | - | -| `"strict-origin-when-cross-origin"` | full | origin | - | -| `"unsafe-url"` | full | full | full | +| Значення | До того ж джерела | До іншого джерела | HTTPS→HTTP | +|-------|----------------|-------------------|------------| +| `"no-referrer"` | - | - | - | +| `"no-referrer-when-downgrade"` | full | full | - | +| `"origin"` | origin | origin | origin | +| `"origin-when-cross-origin"` | full | origin | origin | +| `"same-origin"` | full | - | - | +| `"strict-origin"` | origin | origin | - | +| `"strict-origin-when-cross-origin"` або `""` (типове значення) | full | origin | - | +| `"unsafe-url"` | full | full | full | Припустімо, у нас є зона адміністрування зі структурою URL-адреси, яка не повинна бути відома за межами сайту. diff --git a/5-network/09-resume-upload/article.md b/5-network/09-resume-upload/article.md index 354976bd5..8ab4f4875 100644 --- a/5-network/09-resume-upload/article.md +++ b/5-network/09-resume-upload/article.md @@ -48,7 +48,7 @@ 3. Після цього ми можемо використати метод `slice` об’єкта `Blob`, щоб надіслати файл починаючи з байта вказаного в `startByte`: ```js - xhr.open("POST", "upload", true); + xhr.open("POST", "upload"); // Ідентифікатор файлу, щоб сервер знав, який файл ми завантажуємо xhr.setRequestHeader('X-File-Id', fileId); diff --git a/5-network/11-websocket/article.md b/5-network/11-websocket/article.md index 8ab0283e8..5be0d30eb 100644 --- a/5-network/11-websocket/article.md +++ b/5-network/11-websocket/article.md @@ -56,7 +56,7 @@ socket.onclose = function(event) { }; socket.onerror = function(error) { - alert(`[error] ${error.message}`); + alert(`[error]`); }; ``` @@ -88,11 +88,11 @@ Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q== Sec-WebSocket-Version: 13 ``` -- `Origin` -- джерело клієнтської сторінки, наприклад `https://javascript.info`. Об’єкти WebSocket є перехресними за своєю природою. Немає спеціальних заголовків чи інших обмежень. Старі сервери все одно не можуть обробляти WebSocket, тому проблем із сумісністю немає. Але заголовок "Origin" важливий, оскільки він дозволяє серверу вирішувати, спілкуватися чи ні WebSocket з цим веб-сайтом. +- `Origin` -- джерело клієнтської сторінки, наприклад `https://javascript.info`. Об’єкти WebSocket є перехресними за своєю природою. Немає спеціальних заголовків чи інших обмежень. Старі сервери все одно не можуть обробляти WebSocket, тому проблем із сумісністю немає. Але заголовок `Origin` важливий, оскільки він дозволяє серверу вирішувати, спілкуватися чи ні WebSocket з цим веб-сайтом. - `Connection: Upgrade` -- сигналізує про те, що клієнт хоче змінити протокол. - `Upgrade: websocket` -- запитуваний протокол - "websocket". -- `Sec-WebSocket-Key` -- випадковий ключ, згенерований браузером для забезпечення безпеки. -- `Sec-WebSocket-Version` -- Версія протоколу WebSocket, 13 є поточною. +- `Sec-WebSocket-Key` -- випадковий ключ, згенерований браузером, який використовується для забезпечення підтримки сервером протоколу WebSocket. Він є випадковим, щоб проксі-сервери не кешували будь-які наступні повідомлення. +- `Sec-WebSocket-Version` -- версія протоколу WebSocket, 13 є поточною. ```smart header="Рукостискання WebSocket неможливо емулювати" Ми не можемо використати `XMLHttpRequest` або `fetch` для виконання такого роду HTTP-запитів, оскільки JavaScript заборонено встановлювати ці заголовки. @@ -117,9 +117,9 @@ Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g= Наприклад: -- `Sec-WebSocket-Extensions: deflate-frame` означає, що браузер підтримує стиснення даних. Розширення -- це щось, пов’язане з передачею даних, тобто функціональність, яка розширює протокол WebSocket. Заголовок `Sec-WebSocket-Extensions` автоматично надсилається браузером зі списком усіх розширень, які він підтримує. +- `Sec-WebSocket-Extensions: deflate-frame` означає, що браузер підтримує стиснення даних. Розширення(extension) -- це щось, пов’язане з передачею даних, тобто функціональність, яка розширює протокол WebSocket. Заголовок `Sec-WebSocket-Extensions` автоматично надсилається браузером зі списком усіх розширень, які він підтримує. -- `Sec-WebSocket-Protocol: soap, wamp` означає, що ми хочемо передати не просто будь-які дані, а дані в [SOAP](http://en.wikipedia.org/wiki/SOAP) або WAMP ("The WebSocket Application Messaging Protocol"). Підпротоколи WebSocket зареєстровані в [каталозі IANA](http://www.iana.org/assignments/websocket/websocket.xml). Отже, цей заголовок описує формати даних, які ми збираємося використовувати. +- `Sec-WebSocket-Protocol: soap, wamp` означає, що ми хочемо передати не просто будь-які дані, а дані в [SOAP](http://en.wikipedia.org/wiki/SOAP) або WAMP ("The WebSocket Application Messaging Protocol"). Підпротоколи WebSocket зареєстровані в [каталозі IANA](https://www.iana.org/assignments/websocket/websocket.xml). Отже, цей заголовок описує формати даних, які ми збираємося використовувати. Цей необов’язковий заголовок встановлюється за допомогою другого параметра `new WebSocket` у вигляді масиву підпротоколів. Наприклад, якщо ми хочемо використовувати SOAP або WAMP: diff --git a/5-network/11-websocket/websocket-handshake.svg b/5-network/11-websocket/websocket-handshake.svg index 79fa27761..96c2cd3ef 100644 --- a/5-network/11-websocket/websocket-handshake.svg +++ b/5-network/11-websocket/websocket-handshake.svg @@ -1 +1 @@ -BrowserServerHTTP-request"Hey, server, let's talk WebSocket?"HTTP-response "Okay!"WebSocket protocol \ No newline at end of file +BrowserServerHTTP-request"Hey, server, let's talk WebSocket?"HTTP-response "Okay!"WebSocket protocol \ No newline at end of file diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index 43df2253d..35bfe1763 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -156,7 +156,7 @@ document.cookie = "user=John; expires=" + date; - **`max-age=3600`** -Це ще один параметр, який вказує термін придатності в секундах з поточного моменту. +Це альтернатива `expires`, який вказує термін придатності cookie в секундах з поточного моменту. Як встановлено 0 або від’ємне значення, то файл cookie буде видалено: @@ -282,7 +282,6 @@ document.cookie = "user=John; secure"; Існує безліч бібліотек для роботи з файлами cookie, тому ці функції тут скоріше в демонстраційних цілях. Але повністю робочі. - ### getCookie(name) Найкоротший шлях щоб отримати доступ до файлів cookie це використання [регулярних виразів](info:regular-expressions). diff --git a/6-data-storage/02-localstorage/article.md b/6-data-storage/02-localstorage/article.md index bb271b8e7..3b263e7e6 100644 --- a/6-data-storage/02-localstorage/article.md +++ b/6-data-storage/02-localstorage/article.md @@ -6,7 +6,7 @@ Ми вже маємо `cookies`. Навіщо додаткові об’єкти? -- На відміну від файлів cookies, об’єкти веб-сховища не надсилаються на сервер із кожним запитом. Завдяки цьому ми можемо зберігати набагато більше даних. Більшість браузерів дозволяють принаймні 2 мегабайти даних (або більше), користувач може навіть змінити цей об’єм. +- На відміну від файлів cookies, об’єкти веб-сховища не надсилаються на сервер із кожним запитом. Завдяки цьому ми можемо зберігати набагато більше даних. Більшість браузерів дозволяють принаймні 5 мегабайтів даних (або більше), користувач може навіть змінити цей об’єм. - Крім того, на відміну від файлів cookies, сервер не може маніпулювати об’єктами сховища через HTTP-заголовки. Все зроблено на JavaScript. - Сховище прив’язане до оригінального сайту (домен/протокол/порт). Таким чином, що різні протоколи або субдомени мають різні об’єкти зберігання, і не можуть отримати доступ до даних один одного. diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 33dcf7617..6530df5ed 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -194,13 +194,12 @@ IndexedDB використовує [стандартний алгоритм се ![](indexeddb-structure.svg) - Як побачимо незабаром, ми можемо надати ключ, коли додамо значення до сховища, подібне до `localStorage`. Але коли ми зберігаємо об’єкти, IndexedDB дозволяє налаштувати властивість об’єкта як ключ, що набагато зручніше. Або ми можемо автоматично згенерувати ключі. Але спочатку нам потрібно створити сховище об’єктів. - Синтаксис створення сховища об’єктів: + ```js db.createObjectStore(name[, keyOptions]); ``` @@ -215,6 +214,7 @@ db.createObjectStore(name[, keyOptions]); Якщо ми не постачаємо `keyOptions`, тоді нам потрібно буде надати ключ явно пізніше, коли будемо зберігати об’єкт. Наприклад, це сховище об’єктів використовує властивість `id` як ключ: + ```js db.createObjectStore('books', {keyPath: 'id'}); ``` @@ -224,6 +224,7 @@ db.createObjectStore('books', {keyPath: 'id'}); Це технічне обмеження. За межами обробника ми зможемо додавати/вилучати/оновлювати дані, але сховища об’єктів можна створювати/вилучати/змінювати лише під час оновлення версії. Щоб виконати оновлення версії бази даних, існує два основних підходи: + 1. Ми можемо реалізувати функції оновлення для кожної версії: від 1 до 2, від 2 до 3, від 3 до 4 тощо. Потім у `upgradeneeded` ми можемо порівняти версії (наприклад, стару 2, тепер 4) та запустити крок оновлення для кожної версії покроково, для кожної проміжної версії (2 до 3, потім від 3 до 4). 2. Або ми можемо просто перевірити базу даних: отримати список існуючих сховищ об’єктів як `db.objectStoreNames`. Цим об’єктом є [DOMStringList](https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#domstringlist) який надає метод `contains(name)` для перевірки існування. А потім ми можемо виконувати оновлення залежно від того, що існує, а що ні. @@ -243,7 +244,6 @@ openRequest.onupgradeneeded = function() { }; ``` - Щоб видалити сховище об’єктів: ```js @@ -257,6 +257,7 @@ db.deleteObjectStore('books') Транзакція — це група операцій, які повинні або всі завершитися успішно, або всі невдало. Наприклад, коли людина щось купує, нам потрібно: + 1. Відняти гроші з рахунку. 2. Додати товар до списку. @@ -277,7 +278,7 @@ db.transaction(store[, type]); - `readonly` -- може лише читати дані, типово. - `readwrite` -- може лише читати та записувати дані, але не створювати/видаляти/змінювати сховища об’єктів. -Існує також тип транзакції `versionchange`: такі транзакції можуть робити все, але ми не можемо створити їх вручну. IndexedDB автоматично створює транзакцію `versionchange` під час відкриття бази даних для обробника `updateneeded`. That's why it's a single place where we can update the database structure, create/remove object stores. +Існує також тип транзакції `versionchange`: такі транзакції можуть робити все, але ми не можемо створити їх вручну. IndexedDB автоматично створює транзакцію `versionchange` під час відкриття бази даних для обробника `updateneeded`. Тому це єдине місце, де ми можемо оновлювати структуру бази даних, створювати/видаляти сховища об’єктів. ```smart header="Чому існують різні види транзакцій?" Продуктивність є причиною, чому транзакції мають бути позначені як `readonly` та `readwrite`. @@ -615,6 +616,7 @@ let request = priceIndex.getAll(IDBKeyRange.upperBound(5)); - **`delete(query)`** -- видалити відповідні значення за запитом. Наприклад: + ```js // видалити книгу з id='js' books.delete('js'); @@ -633,6 +635,7 @@ request.onsuccess = function() { ``` Щоб видалити все: + ```js books.clear(); // очистити сховище. ``` @@ -652,6 +655,7 @@ books.clear(); // очистити сховище. Оскільки сховище об’єктів внутрішньо відсортовано за ключем, курсор проходить по сховищу в порядку знаходження ключа (типово за зростанням). Синтаксис: + ```js // як getAll, але з використанням курсору: let request = store.openCursor(query, [direction]); @@ -749,7 +753,6 @@ try { } catch(err) { console.log('error', err.message); } - ``` Тож маємо все солоденьке «простий асинхронний код» та "try..catch". @@ -772,10 +775,8 @@ window.addEventListener('unhandledrejection', event => { ### Підводний камінь «Неактивна транзакція». - Як ми вже знаємо, транзакція автоматично завершується, як тільки браузер закінчить роботу з поточним кодом і мікрозавданнями. Отже, якщо ми помістимо *макрозавдання* на кшталт `fetch` в середині транзакції, то транзакція не чекатиме завершення. Вона просто автоматично завершається. Отже, наступний запит буде невдалим. - Для обгортки промісів і `async/await` ситуація однакова. Ось приклад `fetch` у середині транзакції: @@ -794,6 +795,7 @@ await inventory.add({ id: 'js', price: 10, created: new Date() }); // Помил Наступний `inventory.add` після `fetch` `(*)` не вдається з помилкою "неактивна транзакція", оскільки на той момент транзакція вже завершена та закрита. Обхідний шлях такий же, як і під час роботи з рідною IndexedDB: або зробіть нову транзакцію, або просто розділіть речі. + 1. Підготуйте дані та спершу отримайте все необхідне. 2. Потім збережіть у базі даних. diff --git a/7-animation/1-bezier-curve/article.md b/7-animation/1-bezier-curve/article.md index 067e44ff7..42eed1462 100644 --- a/7-animation/1-bezier-curve/article.md +++ b/7-animation/1-bezier-curve/article.md @@ -4,6 +4,12 @@ Це дуже проста річ, яку варто дослідити один раз, щоб надалі комфортно почуватись у світі векторної графіки та просунутих анімацій. +```smart header="Трохи теорії, будь ласка" +Ця стаття містить теоретичне, але дуже необхідне розуміння того, що таке криві Безьє, а [наступна](info:css-animations#bezier-curve) показує, як ми можемо використовувати їх для CSS анімацій. + +Будь ласка, не поспішайте, щоб прочитати та зрозуміти концепцію, вона вам добре послужить. +``` + ## Контрольні точки [Крива Безьє](https://uk.wikipedia.org/wiki/Крива_Безьє) задається контрольними точками. diff --git a/7-animation/1-bezier-curve/bezier3-draw1.svg b/7-animation/1-bezier-curve/bezier3-draw1.svg index fd3ca092f..b3cf15aed 100644 --- a/7-animation/1-bezier-curve/bezier3-draw1.svg +++ b/7-animation/1-bezier-curve/bezier3-draw1.svg @@ -1 +1 @@ -1320.25t = 0.250.25 \ No newline at end of file +1320.25t = 0.250.25 \ No newline at end of file diff --git a/7-animation/1-bezier-curve/demo.svg b/7-animation/1-bezier-curve/demo.svg index 5240697ee..56d5b3fbe 100644 --- a/7-animation/1-bezier-curve/demo.svg +++ b/7-animation/1-bezier-curve/demo.svg @@ -153,6 +153,9 @@ http://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html#path_C points[i].y = y; setPointCoords(point, i); drawPath(); + if (t > 0) { + drawT(points, t - STEP); + } } document.onmouseup = function() { document.onmousemove = document.onmouseup = null; @@ -212,6 +215,7 @@ http://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html#path_C } } + const STEP = 0.005; let t = 0; let timer; @@ -241,7 +245,7 @@ http://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html#path_C return; } - t += 0.005; + t += STEP; }, 30); } diff --git a/7-animation/2-css-animations/1-animate-logo-css/solution.view/index.html b/7-animation/2-css-animations/1-animate-logo-css/solution.view/index.html index 4e90e2478..d77f25e28 100644 --- a/7-animation/2-css-animations/1-animate-logo-css/solution.view/index.html +++ b/7-animation/2-css-animations/1-animate-logo-css/solution.view/index.html @@ -27,12 +27,12 @@