Skip to content

Commit f4ec8e9

Browse files
Добавляет доку Iterator.prototype.take() (#5917)
* Добавляет скелет статьи * Добавляет пример * Добавляет раздел `Как пишется` * Добавляет раздел `Как понять` * Дополняет раздел `Как понять` * Дополняет и корректирует раздел `Как понять` * Завершает статью * Добавляет новый подраздел * Исправляет после ревью * Добавляет явное преобразование к числу * Корректирует формулировку * Корректирует спорный момент --------- Co-authored-by: Polina Gurtovaia <zloymult@gmail.com>
1 parent cf1ed6d commit f4ec8e9

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed

js/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ groups:
160160
- name: "Итераторы"
161161
items:
162162
- iterator
163+
- iterator-take
163164
- iterator-to-array
164165
- name: "Обработка исключений"
165166
items:

js/iterator-take/index.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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

Comments
 (0)