|
| 1 | +--- |
| 2 | +title: "Iterator.prototype.take()" |
| 3 | +description: "Возвращает итератор, завершающийся после фиксированного числа шагов" |
| 4 | +baseline: |
| 5 | + - group: iterator-methods |
| 6 | + features: |
| 7 | + - javascript.builtins.Iterator.take |
| 8 | +authors: |
| 9 | + - vitya-ne |
| 10 | +related: |
| 11 | + - js/iterator |
| 12 | + - js/iterator-to-array |
| 13 | + - js/generators |
| 14 | +tags: |
| 15 | + - doka |
| 16 | +--- |
| 17 | + |
| 18 | +## Кратко |
| 19 | + |
| 20 | +`Iterator.prototype.take()` создаёт итератор с гарантированным завершением. Метод возвращает итератор, который завершится после указанного числа успешных шагов итерации или раньше, в случае завершения исходного итератора. О том, что такое итератор, можно прочитать в статье [«Итератор»](/js/iterator/). |
| 21 | + |
| 22 | +## Пример |
| 23 | + |
| 24 | +У нас есть коллекция фильмов и итератор для её обхода: |
| 25 | + |
| 26 | +```js |
| 27 | +const movies = [ |
| 28 | + 'Братство кольца', |
| 29 | + 'Две крепости', |
| 30 | + 'Возвращение короля', |
| 31 | + 'Нежданное путешествие' |
| 32 | +] |
| 33 | + |
| 34 | +const baseIterator = movies.values() |
| 35 | +``` |
| 36 | + |
| 37 | +Создадим из `baseIterator` новый итератор, для обхода только части коллекции, например для итерации по фильмам трилогии "Властелин Колец": |
| 38 | + |
| 39 | +```js |
| 40 | +const limitIterator = baseIterator.take(3) |
| 41 | + |
| 42 | +for (const item of limitIterator) { |
| 43 | + console.log(item) |
| 44 | +} |
| 45 | +// Братство кольца |
| 46 | +// Две крепости |
| 47 | +// Возвращение короля |
| 48 | +``` |
| 49 | + |
| 50 | +## Как пишется |
| 51 | + |
| 52 | +`Iterator.prototype.take()` принимает один обязательный аргумент — число, определяющее максимальное количество значений, которые может вернуть созданный итератор. |
| 53 | + |
| 54 | +`Iterator.prototype.take()` возвращает новый итератор. |
| 55 | + |
| 56 | +При выполнении метода произойдёт преобразование аргумента в целое положительное число, поэтому тип аргумента не ограничен `Integer`. Например, равнозначными будут значения: |
| 57 | + |
| 58 | +- 1 |
| 59 | +- "1" |
| 60 | +- `true` |
| 61 | +- [1] |
| 62 | +- 1.7 |
| 63 | + |
| 64 | +Если передать `0` — метод вернёт завершённый итератор. |
| 65 | + |
| 66 | +```js |
| 67 | +const iterator = [ 'Трудности', 'перевода' ].values() |
| 68 | +const limitIterator = iterator.take(0) |
| 69 | + |
| 70 | +for (const item of limitIterator) { |
| 71 | + console.log(item) // Не выполнится |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +Если переданное значение не может быть преобразовано или является отрицательным числом, будет брошена ошибка `RangeError`. Попытка вызвать метод без аргумента так же приведёт к ошибке `RangeError`. |
| 76 | + |
| 77 | +## Как понять |
| 78 | + |
| 79 | +Работая с итераторами можно столкнуться с ситуацией, когда необходимо ограничить количество получаемых значений. Например, это может понадобиться, когда итератор не имеет конечного состояния (`{ done:true }`). Такой итератор называют "бесконечный". |
| 80 | + |
| 81 | +Рассмотрим пример. У нас есть функция-генератор паролей: |
| 82 | + |
| 83 | +```js |
| 84 | +function* passwordGenerator(length = 8) { |
| 85 | + const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()' |
| 86 | + |
| 87 | + while (true) { |
| 88 | + let password = ''; |
| 89 | + for (let i = 0; i < length; i++) { |
| 90 | + const random = Math.floor(Math.random() * charset.length); |
| 91 | + password += charset[random]; |
| 92 | + } |
| 93 | + yield password; |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +При вызове функции будет создан итератор, возвращающий строку-пароль: |
| 99 | + |
| 100 | +```js |
| 101 | +const passwords = passwordGenerator() |
| 102 | + |
| 103 | +console.log(passwords.next().value) |
| 104 | +// Z@1pivgS |
| 105 | +console.log(passwords.next().value) |
| 106 | +// PXoDm)B8 |
| 107 | +``` |
| 108 | + |
| 109 | +`passwords` является итерируемым объектом, но его нельзя обойти с помощью [`for...of`](/js/for-of/) потому что это приведёт к бесконечному циклу. По этой же причине нельзя применить методы получения массива значений: [`Array.from()`](/js/array-from/) или [`Iterator.prototype.toArray()`](/js/iterator-to-array/). |
| 110 | + |
| 111 | +`Iterator.prototype.take()` помогает получить итератор с ограниченным количеством итераций: |
| 112 | + |
| 113 | +```js |
| 114 | + |
| 115 | +const passwords = passwordGenerator() |
| 116 | + |
| 117 | +const limitPasswords = passwords.take(3) |
| 118 | + |
| 119 | +console.log(Array.from(limitPasswords)) |
| 120 | +// [ '1QAg2NHv', 'L(46lQly', 'Vs9c)vWm' ] |
| 121 | +``` |
| 122 | + |
| 123 | +Итератор, созданный методом `Iterator.prototype.take()`, использует исходный итератор как источник данных. Это означает, что каждый шаг итерации выполняется за счёт вызова `next()` у исходного итератора. Если исходный итератор завершится, итерация созданного итератора также завершится. |
| 124 | + |
| 125 | +Посмотрим как вызов `next()` у одного итератора влияет на состояние другого: |
| 126 | + |
| 127 | +```js |
| 128 | +const persons = [ |
| 129 | + 'I Гретель', |
| 130 | + 'II Брунгильда', |
| 131 | + 'III Ирмгард', |
| 132 | + 'IV Адельхейд' |
| 133 | +] |
| 134 | + |
| 135 | +const base = persons.values() |
| 136 | + |
| 137 | +const limit = base.take(3) |
| 138 | + |
| 139 | +base.next() |
| 140 | + |
| 141 | +console.log(limit.next().value) |
| 142 | +// II Брунгильда |
| 143 | + |
| 144 | +console.log(base.next().value) |
| 145 | +// III Ирмгард |
| 146 | + |
| 147 | +console.log(Array.from(limit)) |
| 148 | +// [ 'IV Адельхейд' ] |
| 149 | +``` |
| 150 | + |
| 151 | +Можно сделать выводы: |
| 152 | +- `Iterator.prototype.take()` не создаёт независимую копию с доступом к части значений исходного итератора; |
| 153 | +- Каждый вызов `next()` у одного итератора влияет на состояние другого, потому что они делят общее состояние итерации. |
| 154 | + |
| 155 | +## Подсказки |
| 156 | + |
| 157 | +Если итератор не является наследником глобального объекта `Iterator`, метод `take()` можно вызвать через `call()`. Это пригодится для итераторов, реализованных вручную. |
| 158 | + |
| 159 | +Допустим имеется функция для создания итератора цветов: #0000FF, #00FF00, #FFFF00, #FF0000 |
| 160 | + |
| 161 | +```js |
| 162 | +function createColorIterator() { |
| 163 | + const colors = ['001', '010', '110', '100'] |
| 164 | + let index = 0 |
| 165 | + |
| 166 | + return { |
| 167 | + next() { |
| 168 | + if (index === colors.length) { |
| 169 | + return { done: true } |
| 170 | + } |
| 171 | + const rgb = colors[index++].split('').map(c => Number(c) * 255) |
| 172 | + const value = `rgb(${rgb.join(',')})` |
| 173 | + return { value, done: false } |
| 174 | + } |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +const colors = createColorIterator() |
| 179 | +``` |
| 180 | + |
| 181 | +Вызов `colors.take(2)` приведёт к ошибке `TypeError`, так как итератор `colors` не наследует методы `Iterator.prototype`: |
| 182 | + |
| 183 | +```js |
| 184 | +console.log(colors.take) |
| 185 | +// undefined |
| 186 | +console.log(colors instanceof Iterator) |
| 187 | +// false |
| 188 | +``` |
| 189 | + |
| 190 | +Вызвать `take()` можно с помощью `call()`: |
| 191 | + |
| 192 | +```js |
| 193 | +const limitColors = Iterator.prototype.take.call(colors, 2) |
| 194 | + |
| 195 | +for (const color of limitColors) { |
| 196 | + console.log(color) |
| 197 | +} |
| 198 | +// rgb(0,0,255) |
| 199 | +// rgb(0,255,0) |
| 200 | +``` |
0 commit comments