diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..490051876 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index 8ef373815..1e3da515d 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -22,7 +22,7 @@ Ось так відображається помилка в консолі: -![chrome](chrome.png) +![chrome](chrome.webp) Точний вигляд інструментів розробника може відрізнятися в залежності від вашої версії Chrome, а також налаштування мови. @@ -49,7 +49,11 @@ Safari (стандартний браузер у macOS, не підтримується Windows/Linux) має свої нюанси. Спочатку нам потрібно увімкнути меню "Розробка". +<<<<<<< HEAD Відкрийте Параметри та перейдіть на панель "Експертні". Знизу буде галочка, яку необхідно вибрати: +======= +Open Settings and go to the "Advanced" pane. There's a checkbox at the bottom: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![safari](safari.png) diff --git a/1-js/01-getting-started/4-devtools/chrome.webp b/1-js/01-getting-started/4-devtools/chrome.webp new file mode 100644 index 000000000..bdf067079 Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome.webp differ diff --git a/1-js/01-getting-started/4-devtools/chrome@2.webp b/1-js/01-getting-started/4-devtools/chrome@2.webp new file mode 100644 index 000000000..2aeca5898 Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome@2.webp differ diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 639f68f42..96e83398f 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -88,16 +88,26 @@ let user = 'Іван' *!*var*/!* message = 'Привіт'; ``` +<<<<<<< HEAD Ключове слово `var` *майже* таке, як `let`. Воно теж оголошує змінну, але дещо іншим, "застарілим" способом. Є деякі відмінності між `let` і `var`, але вони поки що не мають для нас значення. Ми дізнаємося більше про ці відмінності в розділі . +======= +The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way. + +There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter . +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```` ## Аналогія з життя Ми легко зрозуміємо концепцію "змінної", якщо уявимо її у вигляді "коробки" для даних з унікальною назвою на наклейці. +<<<<<<< HEAD Наприклад, змінну `message` можна уявити як коробку з написом `"Повідомлення"` зі значенням `"Привіт!"` всередині: +======= +For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![](variable.svg) @@ -197,15 +207,24 @@ let my-name; // дефіс '-' недопустимий в імені Змінні з іменами `apple` і `APPLE` -- це дві різні змінні. ``` +<<<<<<< HEAD ````smart header="Нелатинські букви дозволені, але не рекомендуються" Можна використовувати будь-яку мову, включно з кирилицею або навіть ієрогліфами, наприклад: +======= +````smart header="Non-Latin letters are allowed, but not recommended" +It is possible to use any language, including Cyrillic letters, Chinese logograms and so on, like this: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let назва = '...'; let 我 = '...'; ``` +<<<<<<< HEAD Технічно тут немає помилки. Такі імена дозволені, проте є міжнародна традиція використовувати англійську мову в іменах змінних (наприклад, `yaLyublyuUkrainu` => `iLoveUkraine`). Навіть якщо ми пишемо маленький скрипт, у нього може бути тривале життя попереду. Можливо, людям з інших країн колись доведеться прочитати його. +======= +Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```` ````warn header="Зарезервовані слова" @@ -260,11 +279,19 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // помилка, не можна перевизначати константу! ``` +<<<<<<< HEAD Коли програміст впевнений, що змінна ніколи не буде змінюватися, він може оголосити її через `const`, що гарантує постійність і буде зрозумілим для кожного. +======= +When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ### Константи в верхньому регістрі +<<<<<<< HEAD Широко поширена практика використання констант як псевдонімів для значень, які важко запам’ятати і які відомі до початку виконання скрипту. +======= +There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Такі константи пишуться в верхньому регістрі з використанням підкреслень. @@ -289,7 +316,11 @@ alert(color); // #FF7F00 Коли ми маємо використовувати для констант великі букви, а коли звичайні? Давайте це з’ясуємо. +<<<<<<< HEAD Назва "константа" лише означає, що змінна ніколи не зміниться. Але є константи, які відомі нам до виконання скрипту (наприклад, шістнадцяткове значення для червоного кольору), а є константи, які *вираховуються* в процесі виконання скрипту, але не змінюються після їхнього початкового присвоєння. +======= +Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Наприклад: @@ -297,7 +328,11 @@ alert(color); // #FF7F00 const pageLoadTime = /* час, потрачений на завантаження вебсторінки */; ``` +<<<<<<< HEAD Значення `pageLoadTime` невідоме до завантаження сторінки, тому її ім’я записано звичайними, а не великими буквами. Але це все ще константа, тому що вона не змінює значення після присвоєння. +======= +The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Інакше кажучи, константи з великими буквами використовуються як псевдоніми для "жорстко закодованих" значень. @@ -307,18 +342,31 @@ const pageLoadTime = /* час, потрачений на завантаженн Такі імена повинні мати чіткий і зрозумілий сенс, який описує дані, що в них зберігаються. +<<<<<<< HEAD Іменування змінних -- одна з найважливіших і найскладніших навичок у програмуванні. Швидкий погляд на імена змінних може показати, який код був написаний початківцем, а який досвідченим розробником. У реальному проєкті більшість часу тратиться на змінення і розширення наявної кодової бази, а не на написання чогось цілком нового. Коли ми повертаємося до якогось коду після виконання чогось іншого впродовж тривалого часу, набагато легше знайти інформацію, яку добре позначено. Або, інакше кажучи, коли змінні мають хороші імена. +======= +Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer. + +In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Будь ласка, приділяйте час на обдумування правильного імені для змінної перед її оголошенням. Робіть так, і будете винагороджені. Декілька хороших правил: +<<<<<<< HEAD - Використовуйте імена, які легко прочитати, як-от `userName` або `shoppingCart`. - Уникайте використання абревіатур або коротких імен, таких як `a`, `b` та `c`, окрім тих випадків, коли ви точно знаєте, що так потрібно. - Робіть імена максимально описовими і лаконічними. Наприклад, такі імена погані: `data` і `value`. Такі імена нічого не говорять. Їх можна використовувати лише тоді, коли з контексту очевидно, на які дані або значення посилається змінна. - Погоджуйте з вашою командою (та з самим собою), які терміни будуть використовуватися у проєкті. Якщо відвідувач сайту називається "user", тоді ми маємо давати відповідні імена іншим пов’язаним змінним: `currentUser` або `newUser`, замість `currentVisitor` або `newManInTown`. +======= +- Use human-readable names like `userName` or `shoppingCart`. +- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing. +- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing. +- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Звучить легко? Це дійсно так, проте на практиці створення зрозумілих і коротких імен -- рідкість. Дійте. diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index e5a4f02b3..77e9bbe97 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -94,6 +94,7 @@ const bigInt = 1234567890123456789012345678901234567890n; Через те, що тип `BigInt` рідко використовується, ми не розглядатимемо його в цьому розділі, проте ми винесли його в окремий розділ . Прочитайте його, якщо вам потрібні такі великі числа. +<<<<<<< HEAD ```smart header="Проблеми із сумісністю" Цієї миті, підтримка типу `BigInt` є в останніх версіях Firefox/Chrome/Edge/Safari, але не в IE. @@ -102,6 +103,9 @@ const bigInt = 1234567890123456789012345678901234567890n; На сайті *MDN* є [таблиця сумісності](https://developer.mozilla.org/uk/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Сумісність_з_веб-переглядачами), де показано, які версії браузерів підтримують тип `BigInt`. ## Рядок (string) +======= +## String +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Рядок у JavaScript має бути оточений лапками. 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 8024870bc..5889942ce 100644 --- a/1-js/02-first-steps/16-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -82,7 +82,7 @@ let sayHi = function() { // (1) створити alert( "Привіт" ); }; -let func = sayHi; +let func = sayHi; //(2) // ... ``` diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 5eeb93608..665c6b23c 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -7,7 +7,11 @@ Тому це цілком звичайна ситуація, коли лише частина стандарту реалізована у самому рушії. +<<<<<<< HEAD Хороша сторінка, щоб побачити поточний стан підтримки функцій мови, є тут (вона велика, нам доведеться ще багато вивчати). +======= +A good page to see the current state of support for language features is (it's big, we have a lot to study yet). +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Як програмісти, ми б хотіли використовувати найновіші можливості. Чим більше хороших речей — тим краще! @@ -71,9 +75,13 @@ if (!Math.trunc) { // якщо немає такої функції JavaScript дуже динамічна мова -- скрипти можуть додавати чи оновлювати функції, навіть якщо вони вбудовані. +<<<<<<< HEAD Є два цікавих поліфіла: - [core js](https://github.com/zloirock/core-js), що підтримує багато функціонала, дозволяє включати лише необхідні функції. +======= +One interesting polyfill library is [core-js](https://github.com/zloirock/core-js), which supports a wide range of features and allows you to include only the ones you need. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## Підсумки @@ -83,8 +91,16 @@ JavaScript дуже динамічна мова -- скрипти можуть Наприклад, пізніше (коли достатньо вивчите JavaScript), ви зможете налаштувати систему збору проєкту на основі [webpack](https://webpack.js.org/) із плагіном [babel-loader](https://github.com/babel/babel-loader). +<<<<<<< HEAD Ось хороші ресурси, де можна дізнатися поточний стан підтримки різного функціоналу: - - для чистого JavaScript. - - для браузерних функцій. +======= +Good resources that show the current state of support for various features: +- - for pure JavaScript. +- - for browser-related functions. + +P.S. Google Chrome is usually the most up-to-date with language features, try it if a tutorial demo fails. Most tutorial demos work with any modern browser though. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b P.S. Зазвичай браузер Google Chrome підтримує більшість найновіших функцій мови, спробуйте його, якщо демонстрація не працює. Більшість демонстрацій працюють із сучасними браузерами. diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md index 02fec1c1f..10177be07 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md @@ -4,7 +4,11 @@ importance: 2 # Ланцюг викликів +<<<<<<< HEAD Існує об'єкт `ladder`, що дозволяє підійматися вгору-вниз: +======= +There's a `ladder` object that allows you to go up and down: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let ladder = { @@ -21,7 +25,11 @@ let ladder = { }; ``` +<<<<<<< HEAD Тепер, якщо нам потрібно зробити кілька викликів послідовно, можна зробити це так: +======= +Now, if we need to make several calls in sequence, we can do it like this: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js ladder.up(); @@ -32,10 +40,18 @@ ladder.down(); ladder.showStep(); // 0 ``` +<<<<<<< HEAD Змініть код `up`, `down` і `showStep` так, щоб зробити доступним ланцюг викликів, наприклад: +======= +Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` +<<<<<<< HEAD Такий підхід широко використовується в бібліотеках JavaScript. +======= +Such an approach is widely used across JavaScript libraries. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b 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 4916a24da..bb6ae6c96 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -253,7 +253,11 @@ let obj = { } }; +<<<<<<< HEAD alert(obj + 2); // 22 ("2" + 2), перетворення до примітиву повернуло рядок => Конкатенація +======= +alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` ## Підсумки diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 4a06296fe..3c2dbad2b 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -4,7 +4,11 @@ 1. Звичайні числа в JavaScript, що зберігаються у 64-бітному форматі [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), також відомі як "подвійні точні числа з плаваючою комою". Це числа, які ми використовуємо більшість часу, і про них ми поговоримо в цьому розділі. +<<<<<<< HEAD 2. Числа BigInt, для відображення цілих чисел довільної довжини. Іноді вони потрібні, оскільки звичайне число не може безпечно перевищувати (253-1) або бути менше ніж -(253-1), як ми згадували раніше в розділі . Оскільки числа BigInt використовуються в декількох спеціальних областях, їм присвячено окремий розділ . +======= +2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter . +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b То ж тут ми поговоримо про звичайні числа. Поглибимо наші знання про них. @@ -41,7 +45,11 @@ alert( 7.3e9 ); // 7.3 мільярдів (те ж саме, що й 7300000000 1.23e6 === 1.23 * 1000000; // e6 означає *1000000 ``` +<<<<<<< HEAD Тепер напишемо щось дуже маленьке. Наприклад, 1 мікросекунда (одна мільйонна частина секунди): +======= +Now let's write something very small. Say, 1 microsecond (one-millionth of a second): +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let mсs = 0.000001; @@ -103,13 +111,23 @@ alert( num.toString(16) ); // "ff" alert( num.toString(2) ); // "11111111" ``` +<<<<<<< HEAD `base` може бути від `2` до `36`. За замовчуванням це `10`. +======= +The `base` can vary from `2` to `36`. By default, it's `10`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Загальні випадки використання для цього є: +<<<<<<< HEAD - **base=16** використовується для шістнадцяткових кольорів, кодування символів тощо, цифри можуть бути `0..9` або `A..F`. - **base=2** використовується в основному для налагодження бітових операцій, цифри можуть бути `0` або `1`. - **base=36** є максимальною, цифри можуть бути `0..9` або `A..Z`. Для позначення такого числа в якості цифер використовується увесь латинський алфавіт. Кумедно, але переведення числа в таку систему числення буває корисним коли ми маємо дуже довгий числовий ідентифікатор і хочемо перетворити його на щось коротше, бо хочемо зробити URL коротшим. Для цього достатньо представити його в системі числення з базою `36`: +======= +- **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`. +- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`. +- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole Latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example, to make a short url. Can simply represent it in the numeral system with base `36`: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run alert( 123456..toString(36) ); // "2n9c" @@ -118,7 +136,11 @@ alert( num.toString(2) ); // "11111111" ```warn header="Дві крапки для виклику методу" Зверніть увагу, що дві крапки в `123456..toString(36)` - це не помилка. Якщо ми хочемо викликати метод безпосередньо на число, наприклад `toString` у наведеному вище прикладі, тоді нам потрібно поставити дві крапки `..` після нього. +<<<<<<< HEAD Якби ми помістили одну крапку: `123456.toString(36)`, тоді виникла б помилка, оскільки синтаксис JavaScript передбачає десяткову частину після першої точки. І якщо ми розмістимо ще одну крапку, то JavaScript розпізнає, що десяткова частина порожня, і далі йде метод. +======= +If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now uses the method. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Також можна написати `(123456).toString(36)`. @@ -137,7 +159,11 @@ alert( num.toString(2) ); // "11111111" : Округляє вверх: `3.1` стає `4`, та `-1.1` стає `-1`. `Math.round` +<<<<<<< HEAD : Округляє до найближчого цілого числа: `3.1` стає `3`, `3.6` стає `4`, `3.5` теж округлить до `4`. +======= +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b `Math.trunc` (не підтримується в Internet Explorer) : Видаляє все після десяткової крапки без округлення: `3.1` стає `3`, `-1.1` стає `-1`. @@ -147,8 +173,10 @@ alert( num.toString(2) ); // "11111111" | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |---|---------|--------|---------|---------| |`3.1`| `3` | `4` | `3` | `3` | +|`3.5`| `3` | `4` | `4` | `3` | |`3.6`| `3` | `4` | `4` | `3` | |`-1.1`| `-2` | `-1` | `-1` | `-1` | +|`-1.5`| `-2` | `-1` | `-1` | `-1` | |`-1.6`| `-2` | `-1` | `-2` | `-1` | @@ -188,7 +216,11 @@ alert( num.toString(2) ); // "11111111" alert( num.toFixed(5) ); // "12.34000", додано нулі, щоб зробити рівно 5 цифр ``` +<<<<<<< HEAD Ми можемо перетворити його на число, використовуючи унарний плюс `+num.toFixed(5)` або `Number()`. +======= + We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## Неточні розрахунки @@ -222,7 +254,17 @@ alert( 0.1 + 0.2 ); // 0.30000000000000004 Число зберігається в пам’яті у його двійковій формі, як послідовність бітів - одиниць і нулів. Але дроби на кшталт `0.1`, `0.2`, які виглядають просто в десятковій системі числення, насправді є нескінченними дробами у своїй двійковій формі. +<<<<<<< HEAD Іншими словами, що таке `0.1`? Це одиниця розділена на десять `1/10` -- одна десята. У десятковій системі такі числа досить легко представити, але якщо порівняти його з однією третиною: `1/3`, то ми стикаємось з нескінченним дробом `0.33333(3)`. +======= +```js run +alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 +alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 +alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101 +``` + +What is `0.1`? It is one divided by ten `1/10`, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Отже, поділ на `10` гарантовано працює в десятковій системі, але поділ на `3` - ні. З цієї ж причини в системі двійкових чисел поділ на `2` гарантовано працює, але `1/10` стає нескінченним двійковим дробом. @@ -242,7 +284,11 @@ alert( 0.1.toFixed(20) ); // 0.10000000000000000555 ```smart header="Не тільки JavaScript" Ця ж проблема існує у багатьох інших мовах програмування. +<<<<<<< HEAD PHP, Java, C, Perl, Ruby дають абсолютно однаковий результат, оскільки використовують один цифровий формат. +======= +PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` Чи можемо ми вирішити проблему? Звичайно, найнадійніший метод - округлення результату за допомогою методу [toFixed(n)](https://developer.mozilla.org/uk/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed): @@ -266,7 +312,11 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` +<<<<<<< HEAD Отже, підхід множення/ділення зменшує помилку, але не видаляє її повністю. +======= +So, the multiply/divide approach reduces the error, but doesn't remove it totally. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Іноді можна спробувати уникнути проблем з дробами. Якщо ми маємо справу з магазином, то можемо зберігати ціни в центах замість доларів. Але що робити, якщо ми застосуємо знижку в розмірі 30%? На практиці повністю уникнути дробів вдається досить рідко. Тому просто округляйте їх, щоб відрізати "хвости", коли це потрібно. @@ -288,7 +338,11 @@ JavaScript не викликає помилку в таких випадках. Це тому, що в усіх чисел є один біт для знака. А тому знак можна встановити або не встановити для будь-якого числа, навіть для нуля. +<<<<<<< HEAD У більшості випадків відмінність непомітна, оскільки оператори підходять до них як до однакових. +======= +In most cases, the distinction is unnoticeable, because operators are suited to treat them as the same. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` ## Перевірки: isFinite та isNaN @@ -337,7 +391,11 @@ alert( isFinite(num) ); ````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`. +<<<<<<< HEAD - `Number.isNaN(value)` повертає `true`, якщо аргумент належить до типу `number` і має значення `NaN`. У будь-якому іншому випадку він повертає `false`. +======= +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case, it returns `false`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run alert( Number.isNaN(NaN) ); // true @@ -348,7 +406,11 @@ alert( isFinite(num) ); alert( isNaN("str") ); // true, оскільки isNaN перетворює рядок "str" ​​на число та отримує NaN як результат цього перетворення ``` +<<<<<<< HEAD - `Number.isFinite(value)` повертає `true`, якщо аргумент належить до типу `number` і не є `NaN/Infinity/-Infinity`. У будь-якому іншому випадку він повертає `false`. +======= +- `Number.isFinite(value)` returns `true` if the argument belongs to the `number` type and it is not `NaN/Infinity/-Infinity`. In any other case, it returns `false`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run alert( Number.isFinite(123) ); // true @@ -366,8 +428,13 @@ alert( isFinite(num) ); ```smart header="Порівняння з `Object.is`" Існує спеціальний вбудований метод `Object.is`, який порівнює значення як `===`, але є більш надійним для двох виняткових випадків: +<<<<<<< HEAD 1. Працює з `NaN`: `Object.is(NaN, NaN) === true`, і це добре. 2. Значення `0` і` -0` різні: `Object.is(0, -0) === false`, технічно це правда, оскільки внутрішньо число має біт знаків, який може бути різним, навіть якщо всі інші біти -- нулі. +======= +1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing. +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct because internally the number has a sign bit that may be different even if all other bits are zeroes. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b У всіх інших випадках `Object.is(a, b)` поверне те саме, що й `a === b`. @@ -385,7 +452,11 @@ alert( +"100px" ); // NaN Винятком є пробіли на початку або в кінці рядка, оскільки вони ігноруються. +<<<<<<< HEAD Але в реальному житті ми часто маємо значення в конкретних одиницях, наприклад, `"100px"` або `"12pt"` в CSS. Також у багатьох країнах символ валюти йде після значення, тому у нас є `"19€"` і ми хочемо отримати число з цього. +======= +But in real life, we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries, the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Ось для чого призначені `parseInt` та `parseFloat`. @@ -479,4 +550,8 @@ JavaScript має вбудований [Math](https://developer.mozilla.org/uk/d Більше математичних функцій: +<<<<<<< HEAD - Дивіться об’єкт [Math](https://developer.mozilla.org/uk/docs/Web/JavaScript/Reference/Global_Objects/Math), коли вони вам потрібні. Бібліотека дуже мала, але охоплює основні потреби. +======= +- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small but can cover basic needs. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b diff --git a/1-js/05-data-types/03-string/3-truncate/task.md b/1-js/05-data-types/03-string/3-truncate/task.md index b366456f8..5ad35489d 100644 --- a/1-js/05-data-types/03-string/3-truncate/task.md +++ b/1-js/05-data-types/03-string/3-truncate/task.md @@ -11,7 +11,13 @@ importance: 5 Наприклад: ```js +<<<<<<< HEAD truncate("Що я хотів би розповісти на цю тему:", 20) == "Що я хотів би розпо…" truncate("Всім привіт!", 20) == "Всім привіт!" +======= +truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…" + +truncate("Hi everyone!", 20) == "Hi everyone!" +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 0713db8b7..fcd6ac1c7 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -431,7 +431,11 @@ let matrix = [ [7, 8, 9] ]; +<<<<<<< HEAD alert( matrix[1][1] ); // 5, центральний елемент +======= +alert( matrix[0][1] ); // 2, the second value of the first inner array +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` ## toString 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 c4d9698f4..b5eda8a16 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,6 +1,10 @@ # Методи масивів +<<<<<<< HEAD Масиви пропонують безліч методів. Щоб було простіше, в цьому розділі вони розбиті на групи. +======= +Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## Додавання/видалення елементів @@ -32,11 +36,19 @@ alert( arr.length ); // 3 Начебто, елемент був видалений, але при перевірці виявляється, що масив все ще має 3 елементи `arr.length == 3`. +<<<<<<< HEAD Це нормально, тому що все, що робить `delete obj.key` -- це видаляє значення за ключем `key`. Це нормально для обʼєктів, але для масивів ми звичайно хочемо, щоб інші елементи змістились і зайняли місце, що звільнилося. Ми чекаємо, що масив стане коротшим. +======= +That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Тому слід застосовувати спеціальні методи. +<<<<<<< HEAD Метод [arr.splice](mdn:js/Array/splice) -- це універсальний «швейцарський ніж» для роботи з масивами. Вміє все: додавати, видаляти і замінювати елементи. +======= +The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Його синтаксис: @@ -62,7 +74,11 @@ alert( arr ); // ["I", "JavaScript"] Легко, правда? Починаючи з індексу `1`, він видалив `1` елемент. +<<<<<<< HEAD У наступному прикладі ми видаляємо 3 елементи та замінюємо їх двома іншими: +======= +In the next example, we remove 3 elements and replace them with the other two: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; @@ -84,7 +100,11 @@ let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- масив видалених елементів ``` +<<<<<<< HEAD Метод `splice` також може вставляти елементи без будь-яких видалень. Для цього нам потрібно встановити значення `0` для `deleteCount`: +======= +The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run let arr = ["I", "study", "JavaScript"]; @@ -114,7 +134,11 @@ alert( arr ); // 1,2,3,4,5 ### slice +<<<<<<< HEAD Метод [arr.slice](mdn:js/Array/slice) набагато простіший, ніж схожий на нього `arr.splice`. +======= +The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Його синтаксис: @@ -124,7 +148,11 @@ arr.slice([start], [end]) Він повертає новий масив, копіюючи до нього всі елементи від індексу `start` до `end` (не включаючи `end`). І `start`, і `end` можуть бути відʼємними. В такому випадку відлік буде здійснюватися з кінця масиву. +<<<<<<< HEAD Він подібний до рядкового методу `str.slice`, але замість підрядків створює підмасиви. +======= +It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Наприклад: @@ -206,7 +234,11 @@ alert( arr.concat(arrayLike) ); // 1,2,something,else Його синтаксис: ```js arr.forEach(function(item, index, array) { +<<<<<<< HEAD // ... робимо щось з item +======= + // ... do something with an item +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b }); ``` @@ -239,7 +271,11 @@ arr.forEach(function(item, index, array) { - `arr.indexOf(item, from)` -- шукає `item`, починаючи з індексу `from`, і повертає індекс, на якому був знайдений шуканий елемент, в іншому випадку `-1`. - `arr.includes(item, from)` -- шукає `item`, починаючи з індексу `from`, і повертає `true`, якщо пошук успішний. +<<<<<<< HEAD Зазвичай ці методи використовуються лише з одним аргументом: `item` для пошуку. Типово пошук відбувається з самого початку. +======= +Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Наприклад: @@ -255,7 +291,11 @@ alert( arr.includes(1) ); // true Зверніть увагу, що метод `indexOf` використовує суворе порівняння `===`. Таким чином, якщо ми шукаємо `false`, він знаходить саме `false`, але не нуль. +<<<<<<< HEAD Якщо ми хочемо перевірити наявність `item` в массиві, і нема потреби знати його точний індекс, тоді краще використати `arr.includes`. +======= +If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Метод [arr.lastIndexOf](mdn:js/Array/lastIndexOf) такий самий, як `indexOf`, але шукає справа наліво. @@ -274,12 +314,16 @@ 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. +That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. ```` ### find і findIndex/findLastIndex +<<<<<<< HEAD Уявіть, що у нас є масив обʼєктів. Як нам знайти обʼєкт за певною умовою? +======= +Imagine we have an array of objects. How do we find an object with a specific condition? +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Тут стане в нагоді метод [arr.find(fn)](mdn:js/Array/find). @@ -297,7 +341,11 @@ let result = arr.find(function(item, index, array) { - `index` -- його індекс. - `array` -- сам масив. +<<<<<<< HEAD Якщо функція повертає `true`, пошук припиняється, повертається `item`. Якщо нічого не знайдено, повертається `undefined`. +======= +If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Наприклад, у нас є масив користувачів, кожен з яких має поля `id` та `name`. Давайте знайдемо той де `id == 1`: @@ -313,11 +361,19 @@ let user = users.find(item => item.id == 1); alert(user.name); // John ``` +<<<<<<< HEAD У реальному житті масиви обʼєктів -- звичайна справа, тому метод `find` вкрай корисний. +======= +In real life, arrays of objects are a common thing, so the `find` method is very useful. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Зверніть увагу, що в даному прикладі ми передаємо `find` функцію `item => item.id == 1`, з одним аргументом. Це типово, інші аргументи цієї функції використовуються рідко. +<<<<<<< HEAD Метод [arr.findIndex](mdn:js/Array/findIndex) -- по суті, те ж саме, але повертає індекс, на якому був знайдений елемент, а не сам елемент, і `-1`, якщо нічого не знайдено. +======= +The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Метод [arr.findLastIndex](mdn:js/Array/findLastIndex) схожий на `findIndex`, але шукає справа наліво, подібно до `lastIndexOf`. @@ -450,11 +506,19 @@ alert(arr); // *!*1, 2, 15*/!* Тепер все працює як треба. +<<<<<<< HEAD Візьмімо паузу і подумаємо, що ж відбувається. Згаданий раніше масив `arr` може бути масивом чого завгодно, вірно? Він може містити числа, рядки, обʼєкти або щось ще. У нас є набір якихось елементів. Щоб впорядкувати його, нам потрібна функція, яка визначає порядок, яка знає, як порівнювати його елементи. За замовчуванням елементи сортуються як рядки. +======= +Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Метод `arr.sort(fn)` реалізує загальний алгоритм сортування. Нам не потрібно піклуватися про те, як він працює всередині (в більшості випадків це оптимізоване [швидке сортування](https://en.wikipedia.org/wiki/Quicksort) чи [Timsort](https://en.wikipedia.org/wiki/Timsort)). Реалізується прохід по масиву, порівнюються його елементи за допомогою наданої функції і змінюється їх порядок. Все, що залишається нам, це надати `fn`, яка робить це порівняння. +<<<<<<< HEAD До речі, якщо ми коли-небудь захочемо дізнатися, які елементи порівнюються -- ніщо не заважає нам вивести їх на екран: +======= +By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -526,7 +590,11 @@ alert( arr ); // 5,4,3,2,1 Метод [str.split(delim)](mdn:js/String/split) саме це і робить. Він розбиває рядок на масив по заданому роздільнику `delim`. +<<<<<<< HEAD У прикладі нижче таким роздільником є ​​рядок з коми та пропуску. +======= +In the example below, we split by a comma followed by a space: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run let names = 'Вася, Петя, Маша'; @@ -593,9 +661,15 @@ let value = arr.reduce(function(accumulator, item, index, array) { - `index` -- його індекс, - `array` -- сам масив. +<<<<<<< HEAD При виконанні функції результат її виклику на попередньому елементі масиву передається як перший аргумент. Зрозуміти простіше, якщо думати про перший аргумент як «збирач» результатів попередніх викликів функції. Після закінчення він стає результатом `reduce`. +======= +As the function is applied, the result of the previous function call is passed to the next one as the first argument. + +So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Звучить складно? @@ -664,7 +738,11 @@ arr.reduce((sum, current) => sum + current); Тому рекомендується завжди вказувати початкове значення. +<<<<<<< HEAD Метод [arr.reduceRight](mdn:js/Array/reduceRight) працює аналогічно, але проходить по масиву справа наліво. +======= +The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same but goes from right to left. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## Array.isArray @@ -689,7 +767,11 @@ alert(Array.isArray([])); // true Майже всі методи масиву, які викликають функції -- такі як `find`, `filter`, `map`, за винятком методу `sort`, приймають необовʼязковий параметр `thisArg`. +<<<<<<< HEAD Цей параметр не пояснювався вище, оскільки дуже рідко використовується, але для кращого розуміння теми ми зобовʼязані його розглянути. +======= +That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Ось повний синтаксис цих методів: @@ -748,11 +830,19 @@ alert(soldiers[1].age); // 23 - `slice(start, end)` -- створює новий масив, копіюючи в нього елементи з позиції `start` до `end` (не включаючи `end`). - `concat(...items)` -- повертає новий масив: копіює всі члени поточного масиву і додає до нього `items`. Якщо якийсь із `items` є масивом, тоді беруться його елементи. +<<<<<<< HEAD - Для пошуку серед елементів: - `indexOf/lastIndexOf(item, pos)` -- шукає `item`, починаючи з позиції `pos`, і повертає його індекс або `-1`, якщо нічого не знайдено. - `includes(value)` -- повертає `true`, якщо в масиві є елемент `value`, в іншому випадку `false`. - `find/filter(func)` -- фільтрує елементи через функцію і віддається перше/всі значення, при проходженні яких функція повертає `true`. - `findIndex` схожий на `find`, але повертає індекс замість значення. +======= +- To search among elements: + - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found. + - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. + - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. + - `findIndex` is like `find`, but returns the index instead of a value. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b - Для перебору елементів: - `forEach(func)` -- викликає `func` для кожного елемента. Нічого не повертає. @@ -795,7 +885,11 @@ alert(soldiers[1].age); // 23 Повний список є в [довіднику MDN](mdn:js/Array). +<<<<<<< HEAD На перший погляд, може здатися, що існує дуже багато різних методів, які досить складно запамʼятати. Але це тільки так здається. +======= +At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Уважно вивчіть шпаргалку, представлену вище, а потім, щоб попрактикуватися, вирішите завдання, запропоновані в цьому розділі. Так ви отримаєте необхідний досвід в правильному використанні методів масиву. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index fb0f4ae2f..362b2dd65 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -173,7 +173,11 @@ while (true) { Але ітерований об’єкт може не бути масивом. І навпаки, псевдомасив може бути не ітерованим об’єктом. +<<<<<<< HEAD Наприклад, `range` у прикладі вище є ітерованим об’єктом, але не масивом, тому що він не має індексованих властивостей та `length`. +======= +But an iterable may not be array-like. And vice versa an array-like may not be iterable. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b І ось об’єкт, який є псевдомасивом, але не ітерованим об’єктом: diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index 1da611875..dc9d37910 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -5,19 +5,32 @@ - Об’єкти дозволяють нам створити єдину сутність, яка зберігатиме дані за ключем. - Масиви дозволяють нам зібрати елементи даних у впорядкований список. +<<<<<<< HEAD Однак, коли ми передаємо їх у функцію, нам може знадобитися не все. Функції можуть знадобитися лише певні елементи або властивості. +======= +However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b *Деструктуроване присвоєння* -- це спеціальний синтаксис, що дозволяє нам "розпаковувати" масиви чи об’єкти в купу змінних, оскільки іноді це зручніше. +<<<<<<< HEAD Деструктурування також чудово працює зі складними функціями, які мають багато параметрів, типових значень тощо. Незабаром ми це побачимо. +======= +Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## Деструктурування масиву Ось приклад того, як масив деструктурується на змінні: ```js +<<<<<<< HEAD // у нас є масив з іменем та прізвищем let arr = ["Іван", "Петренко"] +======= +// we have an array with a name and surname +let arr = ["John", "Smith"] +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b *!* // деструктуроване присвоєння @@ -40,10 +53,17 @@ alert(firstName); // Іван alert(surname); // Петренко ``` +<<<<<<< HEAD Як бачите, синтаксис простий. Хоча є кілька особливих деталей. Давайте розглянемо більше прикладів, щоб краще це зрозуміти. ````smart header="\"Деструктурування\" не означає \"руйнування\"." Це називається "деструктуроване присвоєння", оскільки воно "деструктурує" шляхом копіювання елементів у змінні. Однак, сам масив не змінюється. +======= +As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better. + +````smart header="\"Destructuring\" does not mean \"destructive\"." +It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Це просто коротший спосіб написати: ```js @@ -65,7 +85,11 @@ let [firstName, , title] = ["Юлій", "Цезар", "Консул", "Римс alert( title ); // Консул ``` +<<<<<<< HEAD У наведеному вище коді другий елемент масиву пропускається, третій присвоюється `title`, а решта елементів масиву також пропускаються (оскільки для них немає змінних). +======= +In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them). +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```` ````smart header="Працює з будь-якими типами даних, що перебираються у правій стороні" @@ -94,10 +118,17 @@ alert(user.surname); // Петренко ```` +<<<<<<< HEAD ````smart header="Цикл з .entries()" У попередньому розділі, ми бачили метод [Object.entries(obj)](mdn:js/Object/entries). Ми можемо використовувати його з деструктуруванням для циклічного перебору ключів-та-значень об’єкта: +======= +````smart header="Looping with .entries()" +In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method. + +We can use it with destructuring to loop over the keys-and-values of an object: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run let user = { @@ -105,7 +136,11 @@ let user = { age: 30 }; +<<<<<<< HEAD // перебрати циклом ключі-та-значення +======= +// loop over the keys-and-values +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b *!* for (let [key, value] of Object.entries(user)) { */!* @@ -169,9 +204,15 @@ alert(name2); // Цезар let [name1, name2, *!*...rest*/!*] = ["Юлій", "Цезар", *!*"Консул", "Римської Республіки"*/!*]; *!* +<<<<<<< HEAD // rest -- це масив елементів, починаючи з 3-го alert(rest[0]); // Консул alert(rest[1]); // Римської Республіки +======= +// rest is an array of items, starting from the 3rd one +alert(rest[0]); // Consul +alert(rest[1]); // of the Roman Republic +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b alert(rest.length); // 2 */!* ``` @@ -187,7 +228,11 @@ let [name1, name2, *!*...titles*/!*] = ["Юлій", "Цезар", "Консул" ### Типові значення +<<<<<<< HEAD Якщо масив коротший за список змінних зліва, помилок не буде. Відсутні значення вважаються невизначеними: +======= +If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run *!* @@ -418,7 +463,11 @@ alert( title ); // Меню ## Вкладене деструктурування +<<<<<<< HEAD Якщо об’єкт або масив містять інші вкладені об’єкти та масиви, ми можемо використовувати складніші шаблони з лівого боку для вилучення глибших частин. +======= +If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b У наведеному нижче коді `options` містить інший об’єкт у властивості `size` та масив у властивості `items`. Шаблон у лівій частині присвоєння має ту саму структуру для вилучення з них значень: @@ -449,7 +498,11 @@ alert(item1); // Торт alert(item2); // Пончик ``` +<<<<<<< HEAD Усі властивості об’єкта `options`, окрім `extra`, яке відсутнє у лівій частині, призначаються відповідним змінним: +======= +All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![](destructuring-complex.svg) @@ -459,9 +512,15 @@ alert(item2); // Пончик ## Розумні параметри функції +<<<<<<< HEAD Бувають випадки, коли функція має багато параметрів, більшість з яких є необов’язковими. Особливо це стосується користувацьких інтерфейсів. Уявіть собі функцію, яка створює меню. Вона може мати ширину, висоту, назву, список елементів тощо. Нижче наведено поганий спосіб написати таку функцію: +======= +There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, an item list and so on. + +Here's a bad way to write such a function: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -469,7 +528,11 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` +<<<<<<< HEAD У реальному житті проблема полягає в тому, як запам’ятати порядок аргументів. Зазвичай IDE намагаються нам допомогти, особливо якщо код добре задокументований, але все ж... Інша проблема полягає в тому, як викликати функцію, коли більшість параметрів типово в порядку. +======= +In real-life, the problem is how to remember the order of arguments. Usually, IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Можливо так? @@ -534,7 +597,11 @@ function({ }) ``` +<<<<<<< HEAD Тоді для об’єкта параметрів буде змінна `varName` для властивості `incomingProperty` з типовим значенням `defaultValue`. +======= +Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Зверніть увагу, що таке деструктурування передбачає, що `showMenu()` має аргумент. Якщо ми хочемо, щоб усі значення були типовими, ми повинні вказати порожній об’єкт: @@ -561,7 +628,7 @@ showMenu(); // Menu 100 200 - Деструктуроване присвоєння дозволяє миттєво зіставити об’єкт або масив з багатьма змінними. - Повний синтаксис для об’єкта: ```js - let {prop : varName = default, ...rest} = object + let {prop : varName = defaultValue, ...rest} = object ``` Це означає, що властивість `prop` має входити до змінної `varName` і, якщо такої властивості не існує, слід використовувати `типове` значення. @@ -571,9 +638,13 @@ showMenu(); // Menu 100 200 - Повний синтаксис для масиву: ```js - let [item1 = default, item2, ...rest] = array + let [item1 = defaultValue, item2, ...rest] = array ``` +<<<<<<< HEAD Перший елемент переходить до `item1`; другий переходить до `item2`, усі інші утворюють масив `rest`. +======= + The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b - Можна витягувати дані з вкладених масивів/об’єктів, для цього ліва сторона повинна мати ту ж структуру, що й права. 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 ca6c45aa4..397c5a0cf 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,11 @@ welcome(); // Привіт, Гість (вкладений виклик вико Тепер це працює, тому що назва `"func"` -- локальне і знаходиться в середині функції. Воно не береться ззовні (і не доступно звідти). Специфікація гарантує, що воно завжди посилається на поточну функцію. +<<<<<<< HEAD Зовнішній код все ще має свою змінну `sayHi` або `welcome`. А `func` -- це "внутрішнє ім’я функції", яким функція може надійно викликати себе зсередини. +======= +The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to call itself reliably. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```smart header="Це не працює з Function Declaration" Функціональність з "внутрішньою назвою", що описана вище, доступна лише для Function Expression, а не для Function Declaration. Для Function Declaration немає синтаксису для додавання "внутрішньої" назви. diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md index 4d36256ee..4726f50cf 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md @@ -1,5 +1,9 @@ +<<<<<<< HEAD Помилка виникає тому що `askPassword` отримує функції `loginOk/loginFail` без об’єкту. +======= +The error occurs because `askPassword` gets functions `loginOk/loginFail` without the object. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Коли `askPassword` викликає їх, їх контекст втрачено `this=undefined`. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 511294f48..bdb184a76 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -125,7 +125,11 @@ funcUser(); // Іван */!* ``` +<<<<<<< HEAD Тут `func.bind(user)` як "прив’язаний варіант" функції `func`, з прив'язаним `this=user`. +======= +Here `func.bind(user)` is a "bound variant" of `func`, with fixed `this=user`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Всі аргументи передаються початковій функції `func` "як є", наприклад: diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index af195b0ae..11b23226d 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -14,7 +14,11 @@ Хоча і для цього є спеціальний метод: +<<<<<<< HEAD - [Object.create(proto[, descriptors])](mdn:js/Object/create) -- створює порожній об’єкт із заданим `proto` як `[[Prototype]]` і необов’язковими дескрипторами властивостей. +======= +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Наприклад: @@ -116,7 +120,11 @@ alert(obj[key]); // [object Object], не "певне значення"! Тут, якщо користувач вводить `__proto__`, призначення в рядку 4 ігнорується! +<<<<<<< HEAD Це, безумовно, може бути дивним для нерозробника, але досить зрозумілим для нас. Властивість `__proto__` є особливою: вона має бути або об’єктом, або `null`. Рядок не може стати прототипом. Ось чому призначення рядка `__proto__` ігнорується. +======= +That could surely be surprising for a non-developer, but pretty understandable for us. The `__proto__` property is special: it must be either an object or `null`. A string can not become a prototype. That's why assigning a string to `__proto__` is ignored. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Проте ми не намагалися реалізувати таку поведінку. Ми хотіли зберегти пари ключ/значення і при цьому ключ з назвою `"__proto__"` не зберігся. Тому це помилка! @@ -203,7 +211,14 @@ alert(Object.keys(chineseDictionary)); // hello,bye - літеральний синтаксис: `{ __proto__: ... }`, дозволяє вказати кілька властивостей - або [Object.create(proto[, descriptors])](mdn:js/Object/create), дозволяє вказати дескриптори властивостей. +<<<<<<< HEAD `Object.create` забезпечує простий спосіб поверхневого копіювання об’єкта з усіма дескрипторами: +======= + - literal syntax: `{ __proto__: ... }`, allows to specify multiple properties + - or [Object.create(proto[, descriptors])](mdn:js/Object/create), allows to specify property descriptors. + + The `Object.create` provides an easy way to shallow-copy an object with all descriptors: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); 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 1e5ba12b2..9d0fcf660 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -637,7 +637,11 @@ window.onerror = function(message, url, line, col, error) { Глобальний обробник `window.onerror` не передбачений для відновлювання роботи скрипту, а тільки відправлення повідомлення про помилку розробникам. +<<<<<<< HEAD Для логування помилок у таких випадках існують спеціальні вебсервіси: чи . +======= +There are also web-services that provide error-logging for such cases, like or . +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Вони працюють наступним чином: diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index 90a8970a7..f468c7c99 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -56,7 +56,7 @@ settled - усталений, завершений, втихомирений; Нижче приклад конструктора проміса і простої функції-виконавця з кодом-"виробником", що видає результат з затримкою (через `setTimeout`): -```js run +```js let promise = new Promise(function(resolve, reject) { // функція-виробник викликається автоматично, при виклику new Promise @@ -232,7 +232,11 @@ promise.catch(alert); // виведе "Error: Ооооой!" через 1 сек Наприклад зупинка завантаження індикаторів, закриття непотрібних підключень тощо. +<<<<<<< HEAD Подумайте про це як про завершення вечірки. Незалежно від того, була вечірка хорошою чи поганою, скільки друзів на ній було, ми все одно повинні прибрати по її завершенні. +======= +Think of it as a party finisher. Irresepective of whether a party was good or bad, how many friends were in it, we still need (or at least should) do a cleanup after it. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Код може виглядати так: diff --git a/1-js/11-async/08-async-await/04-promise-all-failure/solution.md b/1-js/11-async/08-async-await/04-promise-all-failure/solution.md new file mode 100644 index 000000000..9fda8e000 --- /dev/null +++ b/1-js/11-async/08-async-await/04-promise-all-failure/solution.md @@ -0,0 +1,113 @@ + +The root of the problem is that `Promise.all` immediately rejects when one of its promises rejects, but it do nothing to cancel the other promises. + +In our case, the second query fails, so `Promise.all` rejects, and the `try...catch` block catches this error.Meanwhile, other promises are *not affected* - they independently continue their execution. In our case, the third query throws an error of its own after a bit of time. And that error is never caught, we can see it in the console. + +The problem is especially dangerous in server-side environments, such as Node.js, when an uncaught error may cause the process to crash. + +How to fix it? + +An ideal solution would be to cancel all unfinished queries when one of them fails. This way we avoid any potential errors. + +However, the bad news is that service calls (such as `database.query`) are often implemented by a 3rd-party library which doesn't support cancellation. Then there's no way to cancel a call. + +As an alternative, we can write our own wrapper function around `Promise.all` which adds a custom `then/catch` handler to each promise to track them: results are gathered and, if an error occurs, all subsequent promises are ignored. + +```js +function customPromiseAll(promises) { + return new Promise((resolve, reject) => { + const results = []; + let resultsCount = 0; + let hasError = false; // we'll set it to true upon first error + + promises.forEach((promise, index) => { + promise + .then(result => { + if (hasError) return; // ignore the promise if already errored + results[index] = result; + resultsCount++; + if (resultsCount === promises.length) { + resolve(results); // when all results are ready - successs + } + }) + .catch(error => { + if (hasError) return; // ignore the promise if already errored + hasError = true; // wops, error! + reject(error); // fail with rejection + }); + }); + }); +} +``` + +This approach has an issue of its own - it's often undesirable to `disconnect()` when queries are still in the process. + +It may be important that all queries complete, especially if some of them make important updates. + +So we should wait until all promises are settled before going further with the execution and eventually disconnecting. + +Here's another implementation. It behaves similar to `Promise.all` - also resolves with the first error, but waits until all promises are settled. + +```js +function customPromiseAllWait(promises) { + return new Promise((resolve, reject) => { + const results = new Array(promises.length); + let settledCount = 0; + let firstError = null; + + promises.forEach((promise, index) => { + Promise.resolve(promise) + .then(result => { + results[index] = result; + }) + .catch(error => { + if (firstError === null) { + firstError = error; + } + }) + .finally(() => { + settledCount++; + if (settledCount === promises.length) { + if (firstError !== null) { + reject(firstError); + } else { + resolve(results); + } + } + }); + }); + }); +} +``` + +Now `await customPromiseAllWait(...)` will stall the execution until all queries are processed. + +This is a more reliable approach, as it guarantees a predictable execution flow. + +Lastly, if we'd like to process all errors, we can use either use `Promise.allSettled` or write a wrapper around it to gathers all errors in a single [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) object and rejects with it. + +```js +// wait for all promises to settle +// return results if no errors +// throw AggregateError with all errors if any +function allOrAggregateError(promises) { + return Promise.allSettled(promises).then(results => { + const errors = []; + const values = []; + + results.forEach((res, i) => { + if (res.status === 'fulfilled') { + values[i] = res.value; + } else { + errors.push(res.reason); + } + }); + + if (errors.length > 0) { + throw new AggregateError(errors, 'One or more promises failed'); + } + + return values; + }); +} +``` diff --git a/1-js/11-async/08-async-await/04-promise-all-failure/task.md b/1-js/11-async/08-async-await/04-promise-all-failure/task.md new file mode 100644 index 000000000..74571c43e --- /dev/null +++ b/1-js/11-async/08-async-await/04-promise-all-failure/task.md @@ -0,0 +1,79 @@ + +# Dangerous Promise.all + +`Promise.all` is a great way to parallelize multiple operations. It's especially useful when we need to make parallel requests to multiple services. + +However, there's a hidden danger. We'll see an example in this task and explore how to avoid it. + +Let's say we have a connection to a remote service, such as a database. + +There're two functions: `connect()` and `disconnect()`. + +When connected, we can send requests using `database.query(...)` - an async function which usually returns the result but also may throw an error. + +Here's a simple implementation: + +```js +let database; + +function connect() { + database = { + async query(isOk) { + if (!isOk) throw new Error('Query failed'); + } + }; +} + +function disconnect() { + database = null; +} + +// intended usage: +// connect() +// ... +// database.query(true) to emulate a successful call +// database.query(false) to emulate a failed call +// ... +// disconnect() +``` + +Now here's the problem. + +We wrote the code to connect and send 3 queries in parallel (all of them take different time, e.g. 100, 200 and 300ms), then disconnect: + +```js +// Helper function to call async function `fn` after `ms` milliseconds +function delay(fn, ms) { + return new Promise((resolve, reject) => { + setTimeout(() => fn().then(resolve, reject), ms); + }); +} + +async function run() { + connect(); + + try { + await Promise.all([ + // these 3 parallel jobs take different time: 100, 200 and 300 ms + // we use the `delay` helper to achieve this effect +*!* + delay(() => database.query(true), 100), + delay(() => database.query(false), 200), + delay(() => database.query(false), 300) +*/!* + ]); + } catch(error) { + console.log('Error handled (or was it?)'); + } + + disconnect(); +} + +run(); +``` + +Two of these queries happen to be unsuccessful, but we're smart enough to wrap the `Promise.all` call into a `try..catch` block. + +However, this doesn't help! This script actually leads to an uncaught error in console! + +Why? How to avoid it? \ No newline at end of file diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 51383753a..69b13cc6b 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -96,10 +96,17 @@ say.sayBye('Іван'); 1. Явний список того, що потрібно імпортувати дає коротші імена: `sayHi()` замість `say.sayHi()`. 2. Явний список того, що потрібно імпортувати дає краще розуміння структури коду: що використано та в якому місці. Також дозволяє підтримувати та рефакторити код легше. +<<<<<<< HEAD ```smart header="Не бійтеся імпортувати занадто багато" Сучасні інструменти збірки, такі як [webpack](https://webpack.js.org/) та інші, об’єднують модулі разом і оптимізують їх для прискорення завантаження. Вони також видаляють імпорти, що не використовуються. Наприклад, якщо ви зробите `import * as library` з величезної бібліотеки коду, а потім використаєте лише кілька методів, тоді невикористані методи [не будуть включені](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) у оптимізований бандл. +======= +```smart header="Don't be afraid to import too much" +Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also remove unused imports. + +For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimized bundle. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` ## Імпорт "as" diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md index 79265e86f..ab4c652fd 100644 --- a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md @@ -1,4 +1,5 @@ +<<<<<<< HEAD # WeakRef та FinalizationRegistry ```warn header="\"Приховані\" можливості мови" @@ -31,10 +32,45 @@ user = null; let user = { name: "John" }; // скопіювали сильне посилання на об'єкт у змінну admin +======= +# WeakRef and FinalizationRegistry + +```warn header="\"Hidden\" features of the language" +This article covers a very narrowly focused topic, that most developers extremely rarely encounter in practice (and may not even be aware of its existence). + +We recommend skipping this chapter if you have just started learning JavaScript. +``` + +Recalling the basic concept of the *reachability principle* from the chapter, +we can note that the JavaScript engine is guaranteed to keep values in memory that are accessible or in use. + +For example: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// let's overwrite the value of the user variable +user = null; + +// the reference is lost and the object will be deleted from memory + +``` + +Or a similar, but slightly more complicated code with two strong references: + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// copied the strong reference to the object into the admin variable +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b *!* let admin = user; */!* +<<<<<<< HEAD // перезапишемо значення змінної user user = null; @@ -58,11 +94,37 @@ let user = { name: "John" }; ``` **Слабке посилання** - це посилання на об'єкт або значення, яке *не* запобігає їх видаленню збирачем сміття. Об'єкт або значення можуть бути видалені збирачем сміття у випадку, якщо на них існують тільки слабкі посилання. +======= +// let's overwrite the value of the user variable +user = null; + +// the object is still reachable through the admin variable +``` +The object `{ name: "John" }` would only be deleted from memory if there were no strong references to it (if we also overwrote the value of the `admin` variable). + +In JavaScript, there is a concept called `WeakRef`, which behaves slightly differently in this case. + + +````smart header="Terms: \"Strong reference\", \"Weak reference\"" +**Strong reference** - is a reference to an object or value, that prevents them from being deleted by the garbage collector. Thereby, keeping the object or value in memory, to which it points. + +This means, that the object or value remains in memory and is not collected by the garbage collector as long, as there are active strong references to it. + +In JavaScript, ordinary references to objects are strong references. For example: + +```js +// the user variable holds a strong reference to this object +let user = { name: "John" }; +``` +**Weak reference** - is a reference to an object or value, that does *not* prevent them from being deleted by the garbage collector. +An object or value can be deleted by the garbage collector if, the only remaining references to them are weak references. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```` ## WeakRef +<<<<<<< HEAD ````warn header="Застереження" Перш ніж ми перейдемо до вивчення, варто зазначити, що правильне застосування структур з цієї статті, вимагає дуже ретельного обмірковування, і якщо це можливо, їхнього використання краще уникати. ```` @@ -82,12 +144,34 @@ let user = { name: "John" }; let user = { name: "John" }; // у змінній admin знаходиться слабке посилання на об'єкт +======= +````warn header="Note of caution" +Before we dive into it, it is worth noting that the correct use of the structures discussed in this article requires very careful thought, and they are best avoided if possible. +```` + +`WeakRef` - is an object, that contains a weak reference to another object, called `target` or `referent`. + +The peculiarity of `WeakRef` is that it does not prevent the garbage collector from deleting its referent-object. In other words, a `WeakRef` object does not keep the `referent` object alive. + +Now let's take the `user` variable as the "referent" and create a weak reference from it to the `admin` variable. +To create a weak reference, you need to use the `WeakRef` constructor, passing in the target object (the object you want a weak reference to). + +In our case — this is the `user` variable: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// the admin variable holds a weak reference to the object +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b *!* let admin = new WeakRef(user); */!* ``` +<<<<<<< HEAD На схемі нижче зображено два типи посилань: сильне посилання з використанням змінної `user` і слабке посилання з використанням змінної `admin`: ![](weakref-finalizationregistry-01.svg) @@ -109,12 +193,36 @@ user = null; На цьому етапі, щоб отримати об'єкт з екземпляру `WeakRef`, ми використаємо його `deref()` метод. Метод `deref()` повертає об'єкт-референт, на який посилається `WeakRef`, якщо об'єкт все ще перебуває в пам'яті. Якщо об'єкт було видалено збирачем сміття, - метод `deref()` поверне `undefined`: +======= +The diagram below depicts two types of references: a strong reference using the `user` variable and a weak reference using the `admin` variable: + +![](weakref-finalizationregistry-01.svg) + +Then, at some point, we stop using the `user` variable - it gets overwritten, goes out of scope, etc., while keeping the `WeakRef` instance in the `admin` variable: + +```js +// let's overwrite the value of the user variable +user = null; +``` + +A weak reference to an object is not enough to keep it "alive". When the only remaining references to a referent-object are weak references, the garbage collector is free to destroy this object and use its memory for something else. + +However, until the object is actually destroyed, the weak reference may return it, even if there are no more strong references to this object. +That is, our object becomes a kind of "[Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat)" – we cannot know for sure whether it's "alive" or "dead": + +![](weakref-finalizationregistry-02.svg) + +At this point, to get the object from the `WeakRef` instance, we will use its `deref()` method. + +The `deref()` method returns the referent-object that the `WeakRef` points to, if the object is still in memory. If the object has been deleted by the garbage collector, then the `deref()` method will return `undefined`: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let ref = admin.deref(); if (ref) { +<<<<<<< HEAD // об'єкт усе ще доступний: можемо здійснити будь-які маніпуляції з ним } else { // об'єкт було видалено збирачем сміття @@ -262,11 +370,161 @@ const getCachedImg = weakRefCache(fetchImg); ```js function cleanupCallback(heldValue) { // код колбека очищення +======= + // the object is still accessible: we can perform any manipulations with it +} else { + // the object has been collected by the garbage collector +} +``` + +## WeakRef use cases + +`WeakRef` is typically used to create caches or [associative arrays](https://en.wikipedia.org/wiki/Associative_array) that store resource-intensive objects. +This allows one to avoid preventing these objects from being collected by the garbage collector solely based on their presence in the cache or associative array. + +One of the primary examples - is a situation when we have numerous binary image objects (for instance, represented as `ArrayBuffer` or `Blob`), and we want to associate a name or path with each image. +Existing data structures are not quite suitable for these purposes: + +- Using `Map` to create associations between names and images, or vice versa, will keep the image objects in memory since they are present in the `Map` as keys or values. +- `WeakMap` is ineligible for this goal either: because the objects represented as `WeakMap` keys use weak references, and are not protected from deletion by the garbage collector. + +But, in this situation, we need a data structure that would use weak references in its values. + +For this purpose, we can use a `Map` collection, whose values are `WeakRef` instances referring to the large objects we need. +Consequently, we will not keep these large and unnecessary objects in memory longer than they should be. + +Otherwise, this is a way to get the image object from the cache if it is still reachable. +If it has been garbage collected, we will re-generate or re-download it again. + +This way, less memory is used in some situations. + +## Example №1: using WeakRef for caching + +Below is a code snippet that demonstrates the technique of using `WeakRef`. + +In short, we use a `Map` with string keys and `WeakRef` objects as their values. +If the `WeakRef` object has not been collected by the garbage collector, we get it from the cache. +Otherwise, we re-download it again and put it in the cache for further possible reuse: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { // (1) + const imgCache = new Map(); // (2) + + return (imgName) => { // (3) + const cachedImg = imgCache.get(imgName); // (4) + + if (cachedImg?.deref()) { // (5) + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); // (6) + imgCache.set(imgName, new WeakRef(newImg)); // (7) + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +Let's delve into the details of what happened here: +1. `weakRefCache` - is a higher-order function that takes another function, `fetchImg`, as an argument. In this example, we can neglect a detailed description of the `fetchImg` function, since it can be any logic for downloading images. +2. `imgCache` - is a cache of images, that stores cached results of the `fetchImg` function, in the form of string keys (image name) and `WeakRef` objects as their values. +3. Return an anonymous function that takes the image name as an argument. This argument will be used as a key for the cached image. +4. Trying to get the cached result from the cache, using the provided key (image name). +5. If the cache contains a value for the specified key, and the `WeakRef` object has not been deleted by the garbage collector, return the cached result. +6. If there is no entry in the cache with the requested key, or `deref()` method returns `undefined` (meaning that the `WeakRef` object has been garbage collected), the `fetchImg` function downloads the image again. +7. Put the downloaded image into the cache as a `WeakRef` object. + +Now we have a `Map` collection, where the keys - are image names as strings, and values - are `WeakRef` objects containing the images themselves. + +This technique helps to avoid allocating a large amount of memory for resource-intensive objects, that nobody uses anymore. +It also saves memory and time in case of reusing cached objects. + +Here is a visual representation of what this code looks like: + +![](weakref-finalizationregistry-03.svg) + +But, this implementation has its drawbacks: over time, `Map` will be filled with strings as keys, that point to a `WeakRef`, whose referent-object has already been garbage collected: + +![](weakref-finalizationregistry-04.svg) + +One way to handle this problem - is to periodically scavenge the cache and clear out "dead" entries. +Another way - is to use finalizers, which we will explore next. + + +## Example №2: Using WeakRef to track DOM objects + +Another use case for `WeakRef` - is tracking DOM objects. + +Let's imagine a scenario where some third-party code or library interacts with elements on our page as long as they exist in the DOM. +For example, it could be an external utility for monitoring and notifying about the system's state (commonly so-called "logger" – a program that sends informational messages called "logs"). + +Interactive example: + +[codetabs height=420 src="weakref-dom"] + +When the "Start sending messages" button is clicked, in the so-called "logs display window" (an element with the `.window__body` class), messages (logs) start to appear. + +But, as soon as this element is deleted from the DOM, the logger should stop sending messages. +To reproduce the removal of this element, just click the "Close" button in the top right corner. + +In order not to complicate our work, and not to notify third-party code every time our DOM-element is available, and when it is not, it will be enough to create a weak reference to it using `WeakRef`. + +Once the element is removed from the DOM, the logger will notice it and stop sending messages. + +Now let's take a closer look at the source code (*tab `index.js`*): + +1. Get the DOM-element of the "Start sending messages" button. +2. Get the DOM-element of the "Close" button. +3. Get the DOM-element of the logs display window using the `new WeakRef()` constructor. This way, the `windowElementRef` variable holds a weak reference to the DOM-element. +4. Add an event listener on the "Start sending messages" button, responsible for starting the logger when clicked. +5. Add an event listener on the "Close" button, responsible for closing the logs display window when clicked. +6. Use `setInterval` to start displaying a new message every second. +7. If the DOM-element of the logs display window is still accessible and kept in memory, create and send a new message. +8. If the `deref()` method returns `undefined`, it means that the DOM-element has been deleted from memory. In this case, the logger stops displaying messages and clears the timer. +9. `alert`, which will be called, after the DOM-element of the logs display window is deleted from memory (i.e. after clicking the "Close" button). **Note, that deletion from memory may not happen immediately, as it depends only on the internal mechanisms of the garbage collector.** + + We cannot control this process directly from the code. However, despite this, we still have the option to force garbage collection from the browser. + + In Google Chrome, for example, to do this, you need to open the developer tools (`key:Ctrl` + `key:Shift` + `key:J` on Windows/Linux or `key:Option` + `key:⌘` + `key:J` on macOS), go to the "Performance" tab, and click on the bin icon button – "Collect garbage": + + ![](google-chrome-developer-tools.png) + +
+ This functionality is supported in most modern browsers. After the actions are taken, the alert will trigger immediately. + +## FinalizationRegistry + +Now it is time to talk about finalizers. Before we move on, let's clarify the terminology: + +**Cleanup callback (finalizer)** - is a function that is executed, when an object, registered in the `FinalizationRegistry`, is deleted from memory by the garbage collector. + +Its purpose - is to provide the ability to perform additional operations, related to the object, after it has been finally deleted from memory. + +**Registry** (or `FinalizationRegistry`) - is a special object in JavaScript that manages the registration and unregistration of objects and their cleanup callbacks. + +This mechanism allows registering an object to track and associate a cleanup callback with it. +Essentially it is a structure that stores information about registered objects and their cleanup callbacks, and then automatically invokes those callbacks when the objects are deleted from memory. + +To create an instance of the `FinalizationRegistry`, it needs to call its constructor, which takes a single argument - the cleanup callback (finalizer). + +Syntax: + +```js +function cleanupCallback(heldValue) { + // cleanup callback code +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b } const registry = new FinalizationRegistry(cleanupCallback); ``` +<<<<<<< HEAD Тут: - `cleanupCallback` - колбек очищення, який буде автоматично викликаний при видаленні зареєстрованого об'єкта з пам'яті. @@ -283,21 +541,48 @@ const registry = new FinalizationRegistry(cleanupCallback); - `unregister(unregisterToken)` - метод `unregister` використовується для скасування реєстрації об'єкта в реєстрі. Він приймає один аргумент - `unregisterToken` (токен скасування реєстрації, який був отриманий під час реєстрації об'єкта). Тепер перейдемо до простого прикладу. Скористаємося вже відомим нам об'єктом `user` і створимо екземпляр `FinalizationRegistry`: +======= +Here: + +- `cleanupCallback` - a cleanup callback that will be automatically called when a registered object is deleted from memory. +- `heldValue` - the value that is passed as an argument to the cleanup callback. If `heldValue` is an object, the registry keeps a strong reference to it. +- `registry` - an instance of `FinalizationRegistry`. + +`FinalizationRegistry` methods: + +- `register(target, heldValue [, unregisterToken])` - used to register objects in the registry. + + `target` - the object being registered for tracking. If the `target` is garbage collected, the cleanup callback will be called with `heldValue` as its argument. + + Optional `unregisterToken` – an unregistration token. It can be passed to unregister an object before the garbage collector deletes it. Typically, the `target` object is used as `unregisterToken`, which is the standard practice. +- `unregister(unregisterToken)` - the `unregister` method is used to unregister an object from the registry. It takes one argument - `unregisterToken` (the unregister token that was obtained when registering the object). + +Now let's move on to a simple example. Let's use the already-known `user` object and create an instance of `FinalizationRegistry`: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let user = { name: "John" }; const registry = new FinalizationRegistry((heldValue) => { +<<<<<<< HEAD console.log(`${heldValue} був видалений збирачем сміття.`); }); ``` Потім зареєструємо об'єкт, для якого потрібен колбек очищення, викликавши метод `register`: +======= + console.log(`${heldValue} has been collected by the garbage collector.`); +}); +``` + +Then, we will register the object, that requires a cleanup callback by calling the `register` method: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js registry.register(user, user.name); ``` +<<<<<<< HEAD Реєстр не зберігає сильне посилання на об'єкт, що реєструється, оскільки це б суперечило його призначенню. Якби реєстр зберігав сильне посилання, то об'єкт ніколи б не був видалений збирачем сміття. Якщо ж об'єкт видаляється збирачем сміття, наш колбек очищення може бути викликаний в якийсь момент у майбутньому, з переданим йому `heldValue`: @@ -324,6 +609,34 @@ registry.register(user, user.name); ```js function fetchImg() { // абстрактна функція для завантаження зображень... +======= +The registry does not keep a strong reference to the object being registered, as this would defeat its purpose. If the registry kept a strong reference, then the object would never be garbage collected. + +If the object is deleted by the garbage collector, our cleanup callback may be called at some point in the future, with the `heldValue` passed to it: + +```js +// When the user object is deleted by the garbage collector, the following message will be printed in the console: +"John has been collected by the garbage collector." +``` + +There are also situations where, even in implementations that use a cleanup callback, there is a chance that it will not be called. + +For example: +- When the program fully terminates its operation (for example, when closing a tab in a browser). +- When the `FinalizationRegistry` instance itself is no longer reachable to JavaScript code. + If the object that creates the `FinalizationRegistry` instance goes out of scope or is deleted, the cleanup callbacks registered in that registry might also not be invoked. + +## Caching with FinalizationRegistry + +Returning to our *weak* cache example, we can notice the following: +- Even though the values wrapped in the `WeakRef` have been collected by the garbage collector, there is still an issue of "memory leakage" in the form of the remaining keys, whose values have been collected by the garbage collector. + +Here is an improved caching example using `FinalizationRegistry`: + +```js +function fetchImg() { + // abstract function for downloading images... +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b } function weakRefCache(fetchImg) { @@ -356,6 +669,7 @@ function weakRefCache(fetchImg) { const getCachedImg = weakRefCache(fetchImg); ``` +<<<<<<< HEAD 1. Для керування очищенням "мертвих" записів у кеші, коли пов'язані з ними об'єкти `WeakRef` збираються збирачем сміття, створюємо реєстр очищення `FinalizationRegistry`. Важливим моментом тут є те, що в колбеку очищення повинно перевірятися, чи був запис видалений збирачем сміття і чи не був доданий заново, щоб не видалити "живий" запис. @@ -395,10 +709,52 @@ const getCachedImg = weakRefCache(fetchImg); Основна суть - показати можливий сценарій спільного використання `WeakRef` і `FinalizationRegistry` в реальному житті. Ось як це виглядає: +======= +1. To manage the cleanup of "dead" cache entries, when the associated `WeakRef` objects are collected by the garbage collector, we create a `FinalizationRegistry` cleanup registry. + + The important point here is, that in the cleanup callback, it should be checked, if the entry was deleted by the garbage collector and not re-added, in order not to delete a "live" entry. +2. Once the new value (image) is downloaded and put into the cache, we register it in the finalizer registry to track the `WeakRef` object. + +This implementation contains only actual or "live" key/value pairs. +In this case, each `WeakRef` object is registered in the `FinalizationRegistry`. +And after the objects are cleaned up by the garbage collector, the cleanup callback will delete all `undefined` values. + +Here is a visual representation of the updated code: + +![](weakref-finalizationregistry-05.svg) + +A key aspect of the updated implementation is that finalizers allow parallel processes to be created between the "main" program and cleanup callbacks. +In the context of JavaScript, the "main" program - is our JavaScript-code, that runs and executes in our application or web page. + +Hence, from the moment an object is marked for deletion by the garbage collector, and to the actual execution of the cleanup callback, there may be a certain time gap. +It is important to understand that during this time gap, the main program can make any changes to the object or even bring it back to memory. + +That's why, in the cleanup callback, we must check to see if an entry has been added back to the cache by the main program to avoid deleting "live" entries. +Similarly, when searching for a key in the cache, there is a chance that the value has been deleted by the garbage collector, but the cleanup callback has not been executed yet. + +Such situations require special attention if you are working with `FinalizationRegistry`. + +## Using WeakRef and FinalizationRegistry in practice + +Moving from theory to practice, imagine a real-life scenario, where a user synchronizes their photos on a mobile device with some cloud service +(such as [iCloud](https://en.wikipedia.org/wiki/ICloud) or [Google Photos](https://en.wikipedia.org/wiki/Google_Photos)), +and wants to view them from other devices. In addition to the basic functionality of viewing photos, such services offer a lot of additional features, for example: + +- Photo editing and video effects. +- Creating "memories" and albums. +- Video montage from a series of photos. +- ...and much more. + +Here, as an example, we will use a fairly primitive implementation of such a service. +The main point - is to show a possible scenario of using `WeakRef` and `FinalizationRegistry` together in real life. + +Here is what it looks like: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![](weakref-finalizationregistry-demo-01.png)
+<<<<<<< HEAD Ліворуч знаходиться хмарна бібліотека фотографій (вони відображаються у вигляді мініатюр). Ми можемо вибрати потрібні нам зображення і створити колаж, натиснувши на кнопку "Create collage" у правій частині сторінки. Потім, отриманий результат можна буде завантажити у вигляді зображення. @@ -410,20 +766,41 @@ const getCachedImg = weakRefCache(fetchImg); Нижче ми бачимо, що внутрішній розмір мініатюр становить 240×240 пікселів. Розмір був обраний спеціально для збільшення швидкості завантаження. Крім того, нам не потрібні повнорозмірні фотографії в режимі попереднього перегляду. +======= +On the left side, there is a cloud library of photos (they are displayed as thumbnails). +We can select the images we need and create a collage, by clicking the "Create collage" button on the right side of the page. +Then, the resulting collage can be downloaded as an image. +

+ +To increase page loading speed, it would be reasonable to download and display photo thumbnails in *compressed* quality. +But, to create a collage from selected photos, download and use them in *full-size* quality. + +Below, we can see, that the intrinsic size of the thumbnails is 240x240 pixels. +The size was chosen on purpose to increase loading speed. +Moreover, we do not need full-size photos in preview mode. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![](weakref-finalizationregistry-demo-02.png)
+<<<<<<< HEAD Припустимо, що нам потрібно створити колаж із 4 фотографій: ми вибираємо їх, після чого натискаємо кнопку "Create collage". На цьому етапі вже відома нам функція weakRefCache перевіряє, чи є потрібне зображення в кеші. Якщо ні, то завантажує його з хмари і розміщує в кеш для можливості подальшого використання. І так відбувається для кожного обраного зображення: +======= +Let's assume, that we need to create a collage of 4 photos: we select them, and then click the "Create collage" button. +At this stage, the already known to us weakRefCache function checks whether the required image is in the cache. +If not, it downloads it from the cloud and puts it in the cache for further use. +This happens for each selected image: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b

![](weakref-finalizationregistry-demo-03.gif)
+<<<<<<< HEAD Звернувши увагу що показує консоль, можна побачити, які з фотографій були завантажені з хмари - на це вказує FETCHED_IMAGE. Оскільки це перша спроба створення колажу, це означає, що на даному етапі "слабкий кеш" ще був порожній, а всі фотографії були завантажені з хмари і розміщені в нього. @@ -431,39 +808,69 @@ const getCachedImg = weakRefCache(fetchImg); Це означає, що об'єкт, який зберігається в кеші і на який ми посилаємося, використовуючи слабке посилання, видаляється збирачем сміття. І наш фіналізатор виконується успішно, тим самим видаляючи ключ, за яким зображення зберігалося в кеші. Про це нас повідомляє CLEANED_IMAGE: +======= +Paying attention to the output in the console, you can see, which of the photos were downloaded from the cloud - this is indicated by FETCHED_IMAGE. +Since this is the first attempt to create a collage, this means, that at this stage the "weak cache" was still empty, and all the photos were downloaded from the cloud and put in it. + +But, along with the process of downloading images, there is also a process of memory cleanup by the garbage collector. +This means, that the object stored in the cache, which we refer to, using a weak reference, is deleted by the garbage collector. +And our finalizer executes successfully, thereby deleting the key, by which the image was stored in the cache. +CLEANED_IMAGE notifies us about it: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![](weakref-finalizationregistry-demo-04.jpg)
+<<<<<<< HEAD Далі ми розуміємо, що нам не подобається отриманий колаж, і вирішуємо змінити одне із зображень і створити новий. Для цього достатньо прибрати виділення з непотрібного зображення, вибрати інше, та ще раз натиснути на кнопку "Create collage": +======= +Next, we realize that we do not like the resulting collage, and decide to change one of the images and create a new one. +To do this, just deselect the unnecessary image, select another one, and click the "Create collage" button again: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b

![](weakref-finalizationregistry-demo-05.gif)
+<<<<<<< HEAD Але, цього разу не всі зображення були викачані з мережі, і одне з них було взяте зі слабкого кешу: про це нам говорить повідомлення CACHED_IMAGE. Це означає, що на момент створення колажу збирач сміття ще не видалив наше зображення, і ми сміливо взяли його з кеша, тим самим скоротивши кількість мережевих запитів і прискоривши загальний час процесу створення колажу: +======= +But this time not all images were downloaded from the network, and one of them was taken from the weak cache: the CACHED_IMAGE message tells us about it. +This means that at the time of collage creation, the garbage collector had not yet deleted our image, and we boldly took it from the cache, +thereby reducing the number of network requests and speeding up the overall time of the collage creation process: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b

![](weakref-finalizationregistry-demo-06.jpg)
+<<<<<<< HEAD Давайте ще трохи "пограємо", замінивши одне із зображень ще раз і створивши новий колаж: +======= +Let's "play around" a little more, by replacing one of the images again and creating a new collage: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b

![](weakref-finalizationregistry-demo-07.gif)
+<<<<<<< HEAD Цього разу результат ще більш значний. З 4-ьох обраних зображень, 3-ри з них були взяті зі слабкого кешу, і тільки одне довелося завантажити з мережі. Зниження навантаження на мережу склало близько 75%. Вражає, чи не так? +======= +This time the result is even more impressive. Of the 4 images selected, 3 of them were taken from the weak cache, and only one had to be downloaded from the network. +The reduction in network load was about 75%. Impressive, isn't it? +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b

![](weakref-finalizationregistry-demo-08.jpg)
+<<<<<<< HEAD Звісно, не слід забувати, що така поведінка не є гарантованою, і залежить від конкретної реалізації та роботи збирача сміття. Виходячи з цього, одразу ж виникає цілком логічне запитання: чому б нам не використати звичайний кеш, де ми можемо самі керувати його вмістом, а не покладатися на збирач сміття? @@ -481,3 +888,22 @@ const getCachedImg = weakRefCache(fetchImg); `FinalizationRegistry` - це засіб реєстрації колбеків, які виконуються під час знищення об'єктів, на які більше немає сильних посилань. Це дає змогу звільняти пов'язані з об'єктом ресурси або виконувати інші необхідні операції перед видаленням об'єкта з пам'яті. +======= +Of course, it is important to remember, that such behavior is not guaranteed, and depends on the specific implementation and operation of the garbage collector. + +Based on this, a completely logical question immediately arises: why do not we use an ordinary cache, where we can manage its entities ourselves, instead of relying on the garbage collector? +That's right, in the vast majority of cases there is no need to use `WeakRef` and `FinalizationRegistry`. + +Here, we simply demonstrated an alternative implementation of similar functionality, using a non-trivial approach with interesting language features. +Still, we cannot rely on this example, if we need a constant and predictable result. + +You can [open this example in the sandbox](sandbox:weakref-finalizationregistry). + +## Summary + +`WeakRef` - designed to create weak references to objects, allowing them to be deleted from memory by the garbage collector if there are no longer strong references to them. +This is beneficial for addressing excessive memory usage and optimizing the utilization of system resources in applications. + +`FinalizationRegistry` - is a tool for registering callbacks, that are executed when objects that are no longer strongly referenced, are destroyed. +This allows releasing resources associated with the object or performing other necessary operations before deleting the object from memory. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html index d8c278590..3f68c8c22 100644 --- a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html @@ -1,15 +1,24 @@ +<<<<<<< HEAD +======= + +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b +<<<<<<< HEAD WeakRef DOM Логер +======= + WeakRef DOM Logger +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b
+<<<<<<< HEAD
@@ -18,6 +27,16 @@
Немає повідомлень. +======= + +
+
+

Messages:

+ +
+
+ No messages. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b
diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js index 3fa1bfda6..4678157b1 100644 --- a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js @@ -9,10 +9,15 @@ startMessagesBtn.addEventListener('click', () => { // (4) closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) +<<<<<<< HEAD +======= + +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b const startMessages = (element) => { const timerId = setInterval(() => { // (6) if (element.deref()) { // (7) const payload = document.createElement("p"); +<<<<<<< HEAD payload.textContent = `Повідомлення: Статус системи OK: ${new Date().toLocaleTimeString()}`; element.deref().append(payload); } else { // (8) @@ -21,3 +26,13 @@ const startMessages = (element) => { } }, 1000); }; +======= + payload.textContent = `Message: System status OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("The element has been deleted."); // (9) + clearInterval(timerId); + } + }, 1000); +}; +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b diff --git a/2-ui/3-event-details/6-pointer-events/article.md b/2-ui/3-event-details/6-pointer-events/article.md index 504c5e8cc..5c66b59c6 100644 --- a/2-ui/3-event-details/6-pointer-events/article.md +++ b/2-ui/3-event-details/6-pointer-events/article.md @@ -126,7 +126,11 @@ Таким чином, проблема полягає в тому, що браузер "викрадає" взаємодію: `pointercancel` запускається на початку процесу "перетягування" і події `pointermove` більше не генеруються. ```online +<<<<<<< HEAD Ось drag'n'drop демо з реєстрацією подій вказівника (лише `up/down`, `move` та `cancel`) у `textarea`: +======= +Here's the drag'n'drop demo with logging of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b [iframe src="ball" height=240 edit] ``` diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index 50e0ceef8..01f638635 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -244,7 +244,11 @@ option = new Option(text, value, defaultSelected, selected); - `defaultSelected` -- якщо `true`, то до опції буде додано HTML-атрибут `selected`, - `selected` -- якщо `true`, то опція буде обраною. +<<<<<<< HEAD Різниця між `defaultSelected` та `selected` полягає в тому, що `defaultSelected` встановлює HTML-атрибут (який ми можемо отримати за допомогою `option.getAttribute('selected')`, тоді як `selected` визначає, обрана опція чи ні. +======= +The difference between `defaultSelected` and `selected` is that `defaultSelected` sets the HTML-attribute (that we can get using `option.getAttribute('selected')`), while `selected` sets whether the option is selected or not. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b На практиці зазвичай слід встановлювати значення _обох_ параметрів на `true` або `false`. (Або просто не додавайте їх -- за замовчуванням вони мають значення `false`.) diff --git a/2-ui/4-forms-controls/3-events-change-input/article.md b/2-ui/4-forms-controls/3-events-change-input/article.md index f22cda803..dfd21a644 100644 --- a/2-ui/4-forms-controls/3-events-change-input/article.md +++ b/2-ui/4-forms-controls/3-events-change-input/article.md @@ -95,7 +95,11 @@ Тому більшість браузерів надають безперешкодний доступ для читання/запису до буфера обміну лише в рамках певних дій користувача, таких як копіювання/вставлення тощо. +<<<<<<< HEAD Заборонено генерувати "користувацькі" події буфера обміну з `dispatchEvent` у всіх браузерах, крім Firefox. І навіть якщо нам вдасться відправити таку подію, у специфікації чітко зазначено, що такі "синтетичні" події не повинні надавати доступ до буфера обміну. +======= +It's forbidden to generate "custom" clipboard events with `dispatchEvent` in all browsers except Firefox. And even if we manage to dispatch such event, the specification clearly states that such "synthetic" events must not provide access to the clipboard. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Навіть якщо хтось вирішить зберегти `event.clipboardData` в обробник подій, а потім отримати до нього доступ пізніше -- це не спрацює. 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 f1fc8d176..666818924 100644 --- a/2-ui/99-ui-misc/02-selection-range/article.md +++ b/2-ui/99-ui-misc/02-selection-range/article.md @@ -354,7 +354,11 @@ From – To >>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Як ми знаємо, об’єкти `Range` завжди мають початок(start) перед кінцем(end). diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md index 9df988a7d..c7c291037 100644 --- a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md @@ -40,11 +40,20 @@ console.log(7); Підведемо підсумки, +<<<<<<< HEAD 1. Числа `1` і `7` з’являються відразу, тому що прості виклики `console.log` не використовують жодних черг. 2. Потім, після завершення основного потоку коду, запускається черга мікрозадач. - Він містить команди: `console.log(3); setTimeout(...4); console.log(5)`. - З’являються числа `3` і `5`, а `setTimeout(() => console.log(4))` додає виклик `console.log(4)` в кінець черги макрозадач. - Черга макрозадач тепер така: `console.log(2); console.log(6); console.log(4)`. 3. Після того як черга мікрозадач стає порожньою, виконується черга макрозадач. І він виводить `2`, `6`, `4`. +======= +1. Numbers `1` and `7` show up immediately, because simple `console.log` calls don't use any queues. +2. Then, after the main code flow is finished, the microtask queue runs. + - It has commands: `console.log(3); setTimeout(...4); console.log(5)`. + - Numbers `3` and `5` show up, while `setTimeout(() => console.log(4))` adds the `console.log(4)` call to the end of the macrotask queue. + - The macrotask queue is now: `console.log(2); console.log(6); console.log(4)`. +3. After the microtask queue becomes empty, the macrotask queue executes. It outputs `2`, `6`, `4`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Нарешті, маємо у консолі: `1 7 3 5 2 6 4`. diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index 3b0d686a9..a76b58801 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -17,7 +17,11 @@ - виконати їх, починаючи з найстарішого. 2. Очікувати поки завдання не з’явиться, потім перейти до пункту 1. +<<<<<<< HEAD Це формалізація того, що ми бачимо, гортаючи вебсторінку. Рушій JavaScript більшість часу не робить нічого, він працює лише коли спрацьовує скрипт, обробник подій чи подія. +======= +That's a formalization of what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Приклади завдань: @@ -30,6 +34,7 @@ Може трапитись так, що завдання приходить тоді, коли рушій вже зайнятий, тоді це завдання стає в чергу. +<<<<<<< HEAD Чергу з таких завдань називають "чергою макрозавдань" ("macrotask queue", термін v8): ![](eventLoop.svg) @@ -37,12 +42,27 @@ Наприклад, поки рушій виконує `script`, користувач може порухати мишкою, що спричинить появу події `mousemove`, та може вийти час, запрограмований в `setTimeout` і так далі. Ці завдання сформують чергу, як показано на схемі вище. Задачі з черги виконуються за правилом "перший прийшов – перший пішов". Коли рушій браузера закінчить виконання `script`, він обробить подію `mousemove`, потім виконає обробник `setTimeout` тощо. +======= +The tasks form a queue, the so-called "macrotask queue" ([v8](https://v8.dev/) term): + +![](eventLoop.svg) + +For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated in the picture above. + +Tasks from the queue are processed on a "first come – first served" basis. When the engine browser is done with the `script`, it handles `mousemove` event, then `setTimeout` handler, and so on. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Доволі просто наразі, чи не так? +<<<<<<< HEAD Ще декілька деталей: 1. Рендеринг ніколи не відбувається поки рушій виконує завдання. Не має значення наскільки довго виконується завдання. Зміни в DOM будуть відмальовані лише після завершення завдання. 2. Якщо виконання завдання займає надто багато часу, браузер не зможе виконувати інші завдання, наприклад, обробляти користувацькі події. Тож після недовгого часу "зависання" з’явиться оповіщення "Сторінка не відповідає" і пропозиція вбити процес виконання завдання разом з цілою сторінкою. Таке трапляється коли код містить багато складних обрахунків або виникає програмна помилка, що створює нескінченний цикл. +======= +Two more details: +1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete. +2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after some time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Що ж, це була теорія. Тепер побачимо як можна використати ці знання на практиці. @@ -54,7 +74,11 @@ Поки рушій зайнятий підсвічуванням синтаксису він не може виконувати інші речі, пов’язані з DOM, обробляти користувацькі події тощо. Це може спричинити "зависання" браузера, що є неприйнятним. +<<<<<<< HEAD Ми можемо уникнути проблем шляхом розбивання великого завдання на шматочки. Підсвітити перші 100 рядків, потім поставити `setTimeout` (з нульовою затримкою) для наступних 100 рядків і так далі. +======= +We can avoid problems by splitting the big task into pieces. Highlight the first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Щоб продемонструвати такий підхід, замість підсвічування для спрощення візьмемо функцію, яка рахує від `1` до `1000000000`. diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index e2f9fef13..ee5a23506 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -2,17 +2,31 @@ Файли cookies («куки») -- це невеликі рядки з даними, які зберігаються безпосередньо в браузері. Вони є частиною HTTP протоколу, визначеного специфікацією [RFC 6265](https://tools.ietf.org/html/rfc6265). +<<<<<<< HEAD Файли cookies зазвичай встановлюються вебсервером за допомогою заголовка HTTP `Set-Cookie`. Потім браузер автоматично додаватиме їх при (майже) кожному запиті до відповідного домену використовуючи заголовок HTTP `Cookie`. +======= +Cookies are usually set by a web server using the response `Set-Cookie` HTTP header. Then, the browser automatically adds them to (almost) every request to the same domain using the `Cookie` HTTP header. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Одним з найбільш поширених випадків використання є аутентифікація: +<<<<<<< HEAD 1. При вході в систему, сервер використовує відповідь отриману з заголовка HTTP `Set-Cookie`, щоб додати в файл cookie унікальний "ідентифікатор сесії" . 2. Наступного разу, коли на той самий домен буде відправлено запит, браузер надішле файл cookie використовуючи заголовок HTTP `Cookie`. 3. Таким чином, сервер знає, хто зробив запит. +======= +1. Upon sign-in, the server uses the `Set-Cookie` HTTP header in the response to set a cookie with a unique "session identifier". +2. Next time the request is sent to the same domain, the browser sends the cookie over the net using the `Cookie` HTTP header. +3. So the server knows who made the request. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Також ми маємо доступ до файлів cookies з браузера, використовуючи властивість `document.cookie`. +<<<<<<< HEAD У файлах cookies та їхніх атрибутах є багато тонкощів. В цьому розділі ми детально їх розглянемо. +======= +There are many tricky things about cookies and their attributes. In this chapter, we'll cover them in detail. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## Зчитування з document.cookie @@ -31,17 +45,29 @@ alert( document.cookie ); // cookie1=value1; cookie2=value2;... ``` +<<<<<<< HEAD Значення `document.cookie` складається з пар `name=value` розділених `; `. Кожна пара -- це окремий файл cookie. Щоб знайти певний файл cookie потрібно розділити значення `document.cookie` за допомогою `; `, а потім знайти потрібний ключ за його назвою. Для цього можна використовувати як регулярні вирази, так і функції для роботи з масивами. Залишимо це завдання для самостійної роботи читача. Окрім того, в кінці розділу ви знайдете допоміжні функції для роботи з файлами cookies. +======= +The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie. + +To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that. + +We leave it as an exercise for the reader. Also, at the end of the chapter, you'll find helper functions to manipulate cookies. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## Запис в document.cookie Ми можемо записувати в `document.cookie`. Але це не просто властивості даних, це [аксесори (гетери/сетери)](info:property-accessors). Присвоєння цих властивостей обробляється особливим чином. +<<<<<<< HEAD **Запис в `document.cookie` оновлює лише вказаний файл cookie, та не чіпатиме решту.** +======= +**A write operation to `document.cookie` updates only the cookie mentioned in it and doesn't touch other cookies.** +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Наприклад, цей виклик встановить файл cookie з іменем `user` та значенням `John`: @@ -50,12 +76,20 @@ document.cookie = "user=John"; // оновити лише файл cookie під alert(document.cookie); // показати всі файли cookies ``` +<<<<<<< HEAD Якщо ви запустите цей код, то, швидше за все, побачите декілька файлів cookies. Це тому, що операція `document.cookie=` оновлює не всі файли cookies, а лише вказаний файли з іменем `user`. +======= +If you run it, you will likely see multiple cookies. That's because the `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Технічно, ім’я та значення можуть містити будь-які символи. Щоб зберегти правильне форматування, вони повинні бути кодовані за допомогою вбудованої функції `encodeURIComponent`. ```js run +<<<<<<< HEAD // спеціальні символи (пробіли, кирилиця), потребують кодування +======= +// special characters (spaces) need encoding +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b let name = "my name"; let value = "John Smith"; @@ -66,6 +100,7 @@ alert(document.cookie); // ...; my%20name=John%20Smith ``` +<<<<<<< HEAD ```warn header="Обмеження" Є декілька обмежень: - За допомогою `document.cookie`, ви можете встановити/оновити лише один файл cookie. @@ -76,6 +111,18 @@ alert(document.cookie); // ...; my%20name=John%20Smith Файли cookies мають ряд атрибутів, деякі з них важливі і повинні бути задані. Атрибути перераховуються після пари `key=value` та розділяються `; `, як показано нижче: +======= +```warn header="Limitations" +There are a few limitations: +- You can only set/update a single cookie at a time using `document.cookie`. +- The `name=value` pair, after `encodeURIComponent`, should not exceed 4KB. So we can't store anything huge in a cookie. +- The total number of cookies per domain is limited to around 20+, the exact limit depends on the browser. +``` + +Cookies have several attributes, many of which are important and should be set. + +The attributes are listed after `key=value`, delimited by `;`, like this: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT" @@ -93,7 +140,11 @@ document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT" Типово, файли cookie доступні лише на тому домені на якому були встановлені. +<<<<<<< HEAD Зверніть увагу, що типово файли cookie також не доступні і на піддомені, такому як `forum.site.com`. +======= +Please note, by default, a cookie is not shared with a subdomain, such as `forum.site.com`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js // якщо задати файл cookie на вебсайті site.com ... @@ -105,7 +156,11 @@ alert(document.cookie); // користувача немає ...Проте цю поведінку можна змінити. Якщо ми хочемо дозволити піддоменам на кшталт `forum.site.com` отримувати файли cookie задані на `site.com` -- це також можливо. +<<<<<<< HEAD Для цього, встановлюючи файл cookie за адресою `site.com`, ми повинні явно задати в атрибуті `domain` кореневий домен: `domain=site.com`. Тоді всі піддомени побачать такий файл cookie. +======= +For that to happen, when setting a cookie at `site.com`, we should explicitly set the `domain` attribute to the root domain: `domain=site.com`. Then all subdomains will see such a cookie. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Наприклад: @@ -120,16 +175,25 @@ document.cookie = "user=John; *!*domain=site.com*/!*" alert(document.cookie); // файл cookie user=John існує ``` +<<<<<<< HEAD ```warn header="Застарілий синтаксис" Історично склалося так що, `domain=.site.com`(з крапкою перед `site.com`) спрацює так само, надаючи доступ до файлів cookie з піддоменів. Тепер крапки на початку доменних імен ігноруються, але деякі браузери можуть відмовитися встановлювати файл cookie, що містить такі крапки. ``` Отже, атрибут `domain` робить файли cookie доступними на піддоменах. +======= +```warn header="Legacy syntax" +Historically, `domain=.site.com` (with a dot before `site.com`) used to work the same way, allowing access to the cookie from subdomains. Leading dots in domain names are now ignored, but some browsers may decline to set the cookie containing such dots. +``` + +To summarize, the `domain` attribute allows to make a cookie accessible at subdomains. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## path - **`path=/mypath`** +<<<<<<< HEAD URL-префікс адреси повинен бути абсолютним. Для того щоб файли cookie були доступними зі сторінок за цією адресою. Типово, це поточна сторінка. Якщо в файлі cookie задано `path=/admin`, то він видимий на сторінках `/admin` та `/admin/something`, але не на `/home` або `/adminpage`. @@ -145,6 +209,23 @@ URL-префікс адреси повинен бути абсолютним. Д - **`expires=Tue, 19 Jan 2038 03:14:07 GMT`** Термін придатності файлу cookie визначає час, коли браузер автоматично видалить його (відповідно до часового поясу браузера). +======= +The URL path prefix must be absolute. It makes the cookie accessible for pages under that path. By default, it's the current path. + +If a cookie is set with `path=/admin`, it's visible on pages `/admin` and `/admin/something`, but not at `/home`, `/home/admin` or `/`. + +Usually, we should set `path` to the root: `path=/` to make the cookie accessible from all website pages. If this attribute is not set the default is calculated using [this method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#path_default_value). + +## expires, max-age + +By default, if a cookie doesn't have one of these attributes, it disappears when the browser/tab is closed. Such cookies are called "session cookies" + +To let cookies survive a browser close, we can set either the `expires` or `max-age` attribute. `max-Age` has precedence if both are set. + +- **`expires=Tue, 19 Jan 2038 03:14:07 GMT`** + +The cookie expiration date defines the time when the browser will automatically delete it (according to the browser's time zone). +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Дата має бути вказана саме в такому форматі, в часовому поясі GMT. Щоб отримати правильну дату, можна скористатися `date.toUTCString`. Наприклад, можемо встановити, що термін придатності файлу cookie закінчується через 1 день: @@ -181,7 +262,11 @@ document.cookie = "user=John; max-age=0"; Тобто файли cookie базуються на домені, вони не залежать від протоколів. +<<<<<<< HEAD В разі якщо цей атрибут задано, то файл cookie створений на `https://site.com` не буде доступний на тому ж вебсайті з HTTP-протоколом `http://site.com`. Тому якщо файли cookie містять конфіденційні дані, які в жодному разі не мають бути відправлені по незашифрованому HTTP-протоколу, тоді параметр `secure` це правильний вибір. +======= +With this attribute, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, the `secure` flag is the right thing. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js // припустимо, що зараз ми на https:// @@ -191,16 +276,25 @@ document.cookie = "user=John; secure"; ## samesite +<<<<<<< HEAD Це ще один атрибут безпеки. Він створений щоб захищати від так званих XSRF-атак (міжсайтова підміна запиту). +======= +This is another security attribute `samesite`. It's designed to protect from so-called XSRF (cross-site request forgery) attacks. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Щоб зрозуміти як він працює та в яких випадках може бути корисним, давайте детальніше розглянемо поняття XSRF-атак. ### XSRF-атаки +<<<<<<< HEAD Уявіть, що ви увійшли в свій обліковий запис на сайті `bank.com`. Тобто: у вас є файли cookie з даними аутентифікації від цього сайту. Ваш браузер відправляє їх сайту `bank.com` при кожному запиті, для того щоб той розпізнав вас та виконав всі конфіденційні фінансові операції. +======= +Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request so that it recognizes you and performs all sensitive financial operations. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Тепер, переглядаючи вебсторінки в іншому вікні, ви випадково потрапили на сайт `evil.com`. Цей сайт містить код JavaScript, який відправляє форму `
` на `bank.com` з заповненими полями, які ініціюють транзакцію на рахунок хакера. +<<<<<<< HEAD Браузер відправляє файли cookies щоразу як ви відвідуєте сайт `bank.com`, навіть якщо форма була відправлена з `evil.com`. Тому банк "впізнає" вас та дійсно виконає платіж. ![](cookie-xsrf.svg) @@ -208,32 +302,67 @@ document.cookie = "user=John; secure"; Така атака називається міжсайтова підміна запиту (Cross-Site Request Forgery, XSRF). Звісно, справжні банкові системи захищені від цього. У всіх згенерованих сайтом `bank.com` формах є спеціальне поле, так званий "токен захисту від XSRF", який не може бути ані згенерований зловмисним сайтом, ані вилучений з віддаленої сторінки. Зловмисник може спробувати відправити форму туди, але не може отримати дані назад. Сайт `bank.com` перевіряє кожну отриману форму на наявність даного токену. +======= +The browser sends cookies every time you visit the site `bank.com`, even if the form was submitted from `evil.com`. So the bank recognizes you and performs the payment. + +![](cookie-xsrf.svg) + +This is a so-called "Cross-Site Request Forgery" (in short, XSRF) attack. + +Real banks are protected from it of course. All forms generated by `bank.com` have a special field, a so-called "XSRF protection token", that an evil page can't generate or extract from a remote page. It can submit a form there, but can't get the data back. The site `bank.com` checks for such a token in every form it receives. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Проте такий захист вимагає більше часу на реалізацію. Нам потрібно переконатися, що кожна форма несе в собі обов’язкове поле з токеном, окрім того потрібно перевіряти всі запити. +<<<<<<< HEAD ### Використання атрибуту samesite в файлі cookie Атрибут `samesite` пропонує ще один спосіб захисту від атак, який (в теорії) не вимагає "токен захисту від xsrf". +======= +### Use cookie samesite attribute + +The cookie `samesite` attribute provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b В нього є два можливі значення: +<<<<<<< HEAD - **`samesite=strict` (те саме, що `samesite` без заданого значення)** +======= +- **`samesite=strict`** +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Файли cookie з параметром `samesite=strict` ніколи не відправляються якщо користувач прийшов ззовні (з іншого сайту). +<<<<<<< HEAD Іншими словами, якщо користувач перейшов за посиланням зі свого електронного листа або відправив форму з `evil.com`, або виконав яку завгодно операцію, що походить з іншого домену, файли cookie не відправляться. Якщо файл cookie аутентифікації має атрибут `samesite=strict`, тоді у XSRF-атаки немає шансу на успіх, тому що запит з сайту `evil.com` надходить без файлу cookie. Таким чином, `bank.com` не ідентифікує користувача та не здійснить платіж. Захист доволі надійний. Файли cookie з атрибутом `samesite=strict` відправлятимуться тільки якщо операції надходять з `bank.com`, наприклад відправлення форми з іншої сторінки домену `bank.com`. +======= +In other words, whether a user follows a link from their email, submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent. + +If authentication cookies have the `samesite=strict` attribute, then an XSRF attack has no chance of succeeding, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment. + +The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite=strict` cookie, e.g. a form submission from another page at `bank.com`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Хоча, у цього рішення є дрібний недолік. +<<<<<<< HEAD Коли користувач переходить за легітимним посилання до `bank.com`, як то зі своїх нотаток, то він буде неприємно здивований коли `bank.com` не "впізнає" його. Дійсно, в такому випадку файл cookie з атрибутом `samesite=strict` не відправляється. Цю проблему можна вирішити використанням двох файлів cookie: один для загальної ідентифікації, лише для того щоб сказати: "Привіт, Іван", а інший з параметром `samesite=strict` для операцій з даними. Тоді особа, яка перейшла за посиланням поза межами сайту побачить привітання, проте для того щоб другий файл cookie відправився, платіжні операції повинні бути ініційовані з сайту банку. - **`samesite=lax` (те саме, що `samesite` без заданого значення)** +======= +When a user follows a legitimate link to `bank.com`, like from their notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case. + +We could work around that by using two cookies: one for "general recognition", only to say: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then, a person coming from outside of the site will see a welcome, but payments must be initiated from the bank's website, for the second cookie to be sent. + +- **`samesite=lax` (same as `samesite` without value)** +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b Спрощене рішення яке так само захищає від XSRF-атак, але не руйнує очікування користувача. @@ -242,26 +371,45 @@ document.cookie = "user=John; secure"; Файл cookie з параметром `samesite=lax` відправляється лише тоді, коли виконуються обидві умови: 1. Обраний HTTP-метод безпечний (наприклад GET, але не POST) +<<<<<<< HEAD Повний список безпечних HTTP-методів зібрано в [специфікації RFC7231](https://tools.ietf.org/html/rfc7231#section-4.2.1). По суті, безпечними вважаються методи які використовуються для читання, а не для запису даних. Вони не повинні виконувати жодних операцій редагування даних. Перехід за посиланням це завжди метод GET, тобто безпечний. +======= + The full list of safe HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231#section-4.2.1). These are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b 2. Операція виконує навігацію вищого рівня (змінює URL в адресному полі браузера) +<<<<<<< HEAD Як правило, ця умова виконується, проте якщо навігація відбувається в `