Skip to content
1 change: 1 addition & 0 deletions js/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ groups:
- iterator
- iterator-take
- iterator-to-array
- iterator-filter
- name: "Обработка исключений"
items:
- try-catch
Expand Down
148 changes: 148 additions & 0 deletions js/iterator-filter/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: "Iterator.prototype.filter()"
description: "Возвращает итератор, из которого можно получить только отфильтрованные значения"
baseline:
- group: iterator-methods
features:
- javascript.builtins.Iterator.filter
authors:
- vitya-ne
related:
- js/iterator
- js/iterator-to-array
- js/iterator-take
tags:
- doka
---

## Кратко

`Iterator.prototype.filter()` создаёт итератор только с теми значениями, которые удовлетворяют условию колбэк-функции. Фильтрация значений выполняется по мере необходимости (lazy evaluation). При каждом вызове метода [`next()`](/js/iterator/#kak-ponyat), итератор будет возвращать следующее подходящее значение исходного итератора, определённое с помощью колбэк-функции. О том, что такое итератор, можно прочитать в статье [«Итератор»](/js/iterator/).

## Пример

Представим, что у нас есть итератор для массива чисел.
Необходимо получить только чётные значения:

```js
// Итератор для массива
const iterator = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].values()

// Создаём итератор для получения чётных чисел
const evenIterator = iterator.filter(num => num % 2 === 0)

// Получаем чётные значения
console.log(evenIterator.toArray())
// [ 2, 4, 6, 8, 10 ]
```

## Как пишется

`Iterator.prototype.filter()` принимает один обязательный аргумент — колбэк-функцию.

При вызове колбэк-функция получает аргументы:

- element — текущее значение итератора;
- index — индекс значения;

Функция выполняется для каждого значения, которое извлекается из итератора. Она вызывается не сразу для всех элементов, а только в случае запроса данных (например, при вызове `.next()` или переборе в цикле [`for...of`](/js/for-of/)).

Если результат функции это `false` или другое [falsy](/js/boolean/#vyrazheniya), то колбэк-функция будет вызываться для следующих значений итератора, пока не вернёт истинное значение. В этом случае текущее значение итератора будет возвращено как результат.

Если попытаться выполнить `Iterator.prototype.filter()` без аргумента или передать значение не функцию, будет брошена ошибка `TypeError`.

`Iterator.prototype.filter()` возвращает новый итератор.

## Как понять

Метод `Iterator.prototype.filter()` можно сравнить с методом массивов [`Array.prototype.filter()`](/js/array-filter/). Оба метода применяются для фильтрации значений с помощью колбэк-функции.

В отличии от массивов, методы `Iterator.prototype`, выполняют «ленивые» вычисления (lazy evaluation). Это позволяет не перегружать память, избегать лишних операций, а главное — даёт возможность работать с большими или бесконечными потоками данных (лог-файлы, сенсоры, генераторы).

Рассмотрим, как можно выполнять фильтрацию данных полученных из [генератора](/js/generators/).

У нас есть функция-генератор пин-кодов:

```js
function* pinGenerator(length = 8) {
const charset = '01234567890123456789'

while (true) {
let id = '';
for (let i = 0; i < length; i++) {
const random = Math.floor(Math.random() * charset.length);
id += charset[random];
}
yield id;
}
}
```

При вызове, функция вернёт итератор. Создадим итератор пин-кодов из 4-х цифр и получим несколько значений:

```js
const pins = pinGenerator(4)

console.log(pins.next().value)
// 6147
console.log(pins.next().value)
// 1848
```

Допустим, что для удобства ввода в приложении, необходимо получать пин-коды, в которых одна из цифр повторятся дважды.

Применим фильтрацию к исходному итератору и создадим итератор отфильтрованных значений. Колбэк-функция будет проверять сколько уникальных цифр содержит строка. Если одна из цифр повторяется дважды, размер Set-коллекции будет равен 3:

```js
// Создаём итератор для получения пик-кодов с повторяющейся цифрой
const easyPins = pins.filter(pin => {
const digits = pin.split('')
const uniqueDigits = new Set(digits)

// Если в строке ровно 3 уникальные цифры,
// значит одна цифра повторяется дважды
return uniqueDigits.size === 3
})

console.log(easyPins.next().value)
// 3319
console.log(easyPins.next().value)
// 4008
console.log(easyPins.next().value)
// 0226
```

Оба итератора «бесконечны», так как не имеют конечного состояния (`{ done:true }`).
Фильтрация работает, потому что колбэк-функция начинает выполняться только при запросе отфильтрованных значений и не вызывается для следующих значений итератора после получения истинного (truthy) значения.

Как и другие методы доступные через `Iterator.prototype`, `filter()` выполняет для исходного итератора роль обёртки (wrapper), добавляя логику фильтрации.

Итератор, созданный методом `Iterator.prototype.filter()`, использует исходный итератор как источник данных и разделяет с ним состояние (указатель на текущее значение). Если вызвать `next()` на одном из итераторов, указатель сдвинется для обоих — как для исходного, так и для отфильтрованного.

Рассмотрим связь итераторов на примере получения чётных значений:

```js
// Итератор для массива
const iterator = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].values()

// Создаём итератор для получения чётных чисел
const evenIterator = iterator.filter(num => num % 2 === 0)

// evenIterator выполняет колбэк-функцию для значений 1 и 2
// Получаем первое чётное значение
console.log(evenIterator.next().value)
// 2

// Следующее доступное значение для обоих итераторов — 3
// Получаем два значения исходного итератора: 3 и 4
console.log(iterator.next().value)
console.log(iterator.next().value)
// 3
// 4

// Следующее доступное значение для обоих итераторов — 5
// evenIterator выполняет колбэк-функцию для значений 5 и 6.
// Получаем следующее чётное значение
console.log(evenIterator.next().value)
// 6
```
Loading