diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 37d7e31e5..286eb1d9c 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -1,20 +1,20 @@ -# Iterables +# Ітеративні об’єкти -*Iterable* objects are a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop. +*Ітеративні* об’єкти є узагальненням масивів. Це концепція, яка дозволяє нам зробити будь-який об’єкт придатним для використання в циклі `for..of`. -Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable. +Звичайно, по масивах можна ітеруватися. Але є багато інших вбудованих об’єктів, які також можна ітерувати. Наприклад, рядки також можна ітерувати. -If an object isn't technically an array, but represents a collection (list, set) of something, then `for..of` is a great syntax to loop over it, so let's see how to make it work. +Якщо об’єкт технічно не є масивом, а представляє колекцію (list, set) чогось, то `for..of` -- чудовий синтаксис для його обходу, тому подивімось, як змусити його працювати. ## Symbol.iterator -We can easily grasp the concept of iterables by making one of our own. +Ми можемо легко зрозуміти концепцію ітеративних об’єктів, зробивши її власноруч. -For instance, we have an object that is not an array, but looks suitable for `for..of`. +Наприклад, у нас є об’єкт, який не є масивом, але виглядає придатним для `for..of`. -Like a `range` object that represents an interval of numbers: +Як, наприклад, об’єкт `range`, який представляє інтервал чисел: ```js let range = { @@ -22,18 +22,18 @@ let range = { to: 5 }; -// We want the for..of to work: +// Ми хочемо, щоб for..of працював: // for(let num of range) ... num=1,2,3,4,5 ``` -To make the `range` object iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). +Щоб зробити об’єкт `range` ітерабельним (і таким чином дозволити `for..of` працювати), нам потрібно додати метод до об’єкта з назвою `Symbol.iterator` (спеціальний вбудований символ саме для цього). -1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`. -2. Onward, `for..of` works *only with that returned object*. -3. When `for..of` wants the next value, it calls `next()` on that object. -4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` is the next value. +1. Коли `for..of` запускається, він викликає цей метод один раз (або викликає помилку, якщо цей метод не знайдено). Метод повинен повернути *ітератор* -- об’єкт з методом `next`. +2. Далі `for..of` працює *лише з поверненим об’єктом*. +3. Коли `for..of` хоче отримати наступне значення, він викликає `next()` на цьому об’єкті. +4. Результат `next()` повинен мати вигляд `{done: Boolean, value: any}`, де `done=true` означає, що ітерація завершена, інакше `value` -- це наступне значення. -Here's the full implementation for `range` with remarks: +Ось повна реалізація об’єкту `range` із зауваженнями: ```js run let range = { @@ -41,18 +41,18 @@ let range = { to: 5 }; -// 1. call to for..of initially calls this +// 1. виклик for..of спочатку викликає цю функцію range[Symbol.iterator] = function() { - // ...it returns the iterator object: - // 2. Onward, for..of works only with this iterator, asking it for next values + // ...вона повертає об’єкт ітератора: + // 2. Далі, for..of працює тільки з цим ітератором, запитуючи у нього наступні значення return { current: this.from, last: this.to, - // 3. next() is called on each iteration by the for..of loop + // 3. next() викликається на кожній ітерації циклом for..of next() { - // 4. it should return the value as an object {done:.., value :...} + // 4. він повинен повертати значення як об’єкт {done:.., value :...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { @@ -62,22 +62,22 @@ range[Symbol.iterator] = function() { }; }; -// now it works! +// тепер це працює! for (let num of range) { - alert(num); // 1, then 2, 3, 4, 5 + alert(num); // 1, потім 2, 3, 4, 5 } ``` -Please note the core feature of iterables: separation of concerns. +Будь ласка, зверніть увагу на основну особливість ітеративних об’єктів: розділення проблем. -- The `range` itself does not have the `next()` method. -- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`, and its `next()` generates values for the iteration. +- Сам `range` не має методу `next()`. +- Натомість інший об’єкт, так званий "ітератор", створюється за допомогою виклику `range[Symbol.iterator]()`, а його `next()` генерує значення для ітерації. -So, the iterator object is separate from the object it iterates over. +Отже, об’єкт, що ітерує відокремлений від об’єкта, який він ітерує. -Technically, we may merge them and use `range` itself as the iterator to make the code simpler. +Технічно, ми можемо об’єднати їх і використовувати `range` в якості ітератора, щоб зробити код простішим. -Like this: +Подібно до цього: ```js run let range = { @@ -99,55 +99,55 @@ let range = { }; for (let num of range) { - alert(num); // 1, then 2, 3, 4, 5 + alert(num); // 1, тоді 2, 3, 4, 5 } ``` -Now `range[Symbol.iterator]()` returns the `range` object itself: it has the necessary `next()` method and remembers the current iteration progress in `this.current`. Shorter? Yes. And sometimes that's fine too. +Тепер `range[Symbol.iterator]()` повертає сам об’єкт `range`: він має необхідний `next()` метод і пам’ятає поточну ітерацію прогресу в `this.current`. Коротше? Так. А іноді це також добре. -The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself. But two parallel for-ofs is a rare thing, even in async scenarios. +Недоліком є те, що тепер неможливо мати два `for..of` цикли паралельно для проходження через об’єкт: вони будуть ділити ітераційний стан, тому що є тільки один ітератор -- сам об’єкт. Але два паралельних for-of це рідкісний випадок, навіть у асинхронізованих сценаріях. ```smart header="Infinite iterators" -Infinite iterators are also possible. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful. +Також можливі нескінченні ітератори. Наприклад, `range` стає нескінченним для `range.to = Infinity`. Або ми можемо зробити ітерований об’єкт, який генерує нескінченну послідовність псевдорандомних чисел. Це також може бути корисним. -There are no limitations on `next`, it can return more and more values, that's normal. +Немає обмежень на `next`, він може повертати все більше і більше значень, це нормально. -Of course, the `for..of` loop over such an iterable would be endless. But we can always stop it using `break`. +Звичайно, `for..of` цикли через такий об’єкт буде нескінченним. Але ми завжди можемо зупинити його за допомогою `break`. ``` -## String is iterable +## Рядок є ітерованим -Arrays and strings are most widely used built-in iterables. +Масиви та рядки найбільш широко використовуються вбудовані ітератори. -For a string, `for..of` loops over its characters: +Для рядка, `for..of` цикл проходить по символам: ```js run for (let char of "test") { - // triggers 4 times: once for each character - alert( char ); // t, then e, then s, then t + // викликається 4 рази: один раз для кожного символу + alert( char ); // t, потім e, потім s, потім t } ``` -And it works correctly with surrogate pairs! +І це правильно працює з сурогатними парами! ```js run let str = '𝒳😂'; for (let char of str) { - alert( char ); // 𝒳, and then 😂 + alert( char ); // 𝒳, і потім 😂 } ``` -## Calling an iterator explicitly +## Виклик ітератора явно -For deeper understanding, let's see how to use an iterator explicitly. +Для глибшого розуміння, подивімось, як явно використовувати ітератор. -We'll iterate over a string in exactly the same way as `for..of`, but with direct calls. This code creates a string iterator and gets values from it "manually": +Ми будемо ітерувати рядок точно так само, як для `for..of`, але з прямими викликами. Цей код створює ітератор рядка і отримує значення від нього "вручну": ```js run -let str = "Hello"; +let str = "Привіт"; -// does the same as +// робить те ж саме, як // for (let char of str) alert(char); *!* @@ -157,97 +157,96 @@ let iterator = str[Symbol.iterator](); while (true) { let result = iterator.next(); if (result.done) break; - alert(result.value); // outputs characters one by one + alert(result.value); // виводить символи один за одним } ``` -That is rarely needed, but gives us more control over the process than `for..of`. For instance, we can split the iteration process: iterate a bit, then stop, do something else, and then resume later. +Це рідко потрібно, але дає нам більше контролю над процесом, ніж `for ..of`. Наприклад, ми можемо розділити процес ітерації: трохи ітерувати, а потім зупинитися, зробити щось інше, а потім відновити пізніше. -## Iterables and array-likes [#array-like] +## Ітеровані об’єкти та псевдомасиви [#array-like] -Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion. +Ці два офіційних терміни виглядають подібними, але дуже різні. Будь ласка, переконайтеся, що ви добре розумієте їх, щоб уникнути плутанини. -- *Iterables* are objects that implement the `Symbol.iterator` method, as described above. -- *Array-likes* are objects that have indexes and `length`, so they look like arrays. +- *Ітеровані* -- це об’єкти, які реалізують метод `Symbol.iterator`, як описано вище. +- *Псевдомасиви* -- це об’єкти, які мають індекси та `length`, тому вони виглядають як масиви. -When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both. +Коли ми використовуємо JavaScript для практичних завдань у браузері або будь-якому іншому середовищі, ми можемо зустріти об’єкти, які є ітерованими або масивами, або обома. +Наприклад, рядки є ітерованими об’єктами (`for..of` працює на них) та псевдомасивами (у них є числові індекси та `length`). -For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`). +Але ітерований об’єкт може не бути масивом. І навпаки, псевдомасив може бути не ітерованим об’єктом. -But an iterable may be not array-like. And vice versa an array-like may be not iterable. +Наприклад, `range` у прикладі вище є ітерованим об’єктом, але не масивом, тому що він не має індексованих властивостей та `length`. -For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`. - -And here's the object that is array-like, but not iterable: +І ось об’єкт, який є псевдомасивом, але не ітерованим об’єктом: ```js run -let arrayLike = { // has indexes and length => array-like +let arrayLike = { // має індекси та length => псевдомасив 0: "Hello", 1: "World", length: 2 }; *!* -// Error (no Symbol.iterator) +// Помилка (немає Symbol.iterator) for (let item of arrayLike) {} */!* ``` -Both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we have such an object and want to work with it as with an array. E.g. we would like to work with `range` using array methods. How to achieve that? +Обидва, ітерований об’єкт та псевдомасив, як правило є *не масивами*, вони не мають `push`,` pop` та ін. Це досить незручно, якщо у нас є такий об’єкт і ми хочемо працювати з ним як з масивом. Наприклад, ми хотіли б працювати з `angy` за допомогою методів масиву. Як цього досягти? ## Array.from -There's a universal method [Array.from](mdn:js/Array/from) that takes an iterable or array-like value and makes a "real" `Array` from it. Then we can call array methods on it. +Існує універсальний метод [Array.from](mdn:js/Array/from), який приймає ітерований об’єкт або псевдомасив і робить з нього "справжній" масив. Тоді ми можемо викликати на ньому методи масиву. -For instance: +Наприклад: ```js run let arrayLike = { - 0: "Hello", - 1: "World", + 0: "Привіт", + 1: "Світ", length: 2 }; *!* let arr = Array.from(arrayLike); // (*) */!* -alert(arr.pop()); // World (method works) +alert(arr.pop()); // Світ (метод працює) ``` -`Array.from` at the line `(*)` takes the object, examines it for being an iterable or array-like, then makes a new array and copies all items to it. +`Array.from` у рядку `(*)` бере об’єкт, перевіряє його на ітерабельність або те, що це псевдомасив, потім створює новий масив і копіює до нього всі елементи. -The same happens for an iterable: +Те ж саме відбувається і з ітерованим об’єктом: ```js -// assuming that range is taken from the example above +// припустимо, що діапазон взятий з наведеного вище прикладу let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (array toString conversion works) ``` -The full syntax for `Array.from` also allows us to provide an optional "mapping" function: +Повний синтаксис для `Array.from` також дозволяє нам надати додаткову функцію "трансформації": ```js Array.from(obj[, mapFn, thisArg]) ``` The optional second argument `mapFn` can be a function that will be applied to each element before adding it to the array, and `thisArg` allows us to set `this` for it. -For instance: +Наприклад: ```js -// assuming that range is taken from the example above +// припустимо, що діапазон взятий з наведеного вище прикладу -// square each number +// порахуємо квадрат кожного числа let arr = Array.from(range, num => num * num); alert(arr); // 1,4,9,16,25 ``` -Here we use `Array.from` to turn a string into an array of characters: +Тут ми використовуємо `Array.from`, щоб перетворити рядок у масив символів: ```js run let str = '𝒳😂'; -// splits str into array of characters +// розіб’ємо рядок на масив символів let chars = Array.from(str); alert(chars[0]); // 𝒳 @@ -255,14 +254,14 @@ alert(chars[1]); // 😂 alert(chars.length); // 2 ``` -Unlike `str.split`, it relies on the iterable nature of the string and so, just like `for..of`, correctly works with surrogate pairs. +На відміну від `str.split`, він спирається на ітерабельний характер рядка і тому, так само, як `for..of`, коректно працює з сурогатними парами. -Technically here it does the same as: +Технічно тут це відбувається так само, як: ```js run let str = '𝒳😂'; -let chars = []; // Array.from internally does the same loop +let chars = []; // Array.from внутрішньо робить цей самий цикл for (let char of str) { chars.push(char); } @@ -270,9 +269,9 @@ for (let char of str) { alert(chars); ``` -...But it is shorter. +...Але це коротше. -We can even build surrogate-aware `slice` on it: +Ми навіть можемо побудувати на ньому `slice`, що підтримує сурогатні пари: ```js run function slice(str, start, end) { @@ -283,25 +282,26 @@ let str = '𝒳😂𩷶'; alert( slice(str, 1, 3) ); // 😂𩷶 -// the native method does not support surrogate pairs -alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs) +// нативний метод не підтримує сурогатні пари +alert( str.slice(1, 3) ); // сміття (дві частини з різних сурогатних пар) ``` -## Summary +## Підсумки -Objects that can be used in `for..of` are called *iterable*. +Об’єкти, які можна використовуватися у `for..of`, називаються *ітерованими*. -- Technically, iterables must implement the method named `Symbol.iterator`. - - The result of `obj[Symbol.iterator]()` is called an *iterator*. It handles further iteration process. +- Технічно ітеровані об’єкти повинні реалізовувати метод з назвою `Symbol.iterator`. + - Результат `obj[Symbol.iterator]()` називається *ітератором*. Він забезпечує подальший процес ітерації. - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value. -- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly. -- Built-in iterables like strings or arrays, also implement `Symbol.iterator`. -- String iterator knows about surrogate pairs. + - Ітератор повинен мати метод з назвою `next()`, який повертає об’єкт `{done: Boolean, value: any}`, де `done: true` означає кінець процесу ітерації, інакше `value` є наступним значенням. +- Метод `Symbol.iterator` автоматично викликається `for..of`, але ми також можемо це зробити безпосередньо. +- Вбудовані ітеровані об’єкти, такі як рядки або масиви, також реалізують `Symbol.iterator`. +- Рядковий ітератор знає про сурогатні пари. -Objects that have indexed properties and `length` are called *array-like*. Such objects may also have other properties and methods, but lack the built-in methods of arrays. +Об’єкти, які мають індексовані властивості та `length`, називаються *псевдомасивами*. Такі об’єкти також можуть мати інші властивості та методи, але не мають вбудованих методів масивів. -If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract. +Якщо ми заглянемо в специфікацію -- ми побачимо, що більшість вбудованих методів припускають, що вони працюють з ітерованими об’єктами або псевдомасивами замість "реальних" масивів, тому що це більш абстрактно. -`Array.from(obj[, mapFn, thisArg])` makes a real `Array` from an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. +`Array.from(obj[, mapFn, thisArg])` створює справжній `Array` з ітерованого об’єкту або псевдомасиву `obj`, і тоді ми можемо використовувати на ньому методи масиву. Необов’язкові аргументи `mapFn` та` thisArg` дозволяють нам застосовувати функції до кожного елемента.