diff --git a/js/index.md b/js/index.md index 305a346235..5caf29bea1 100644 --- a/js/index.md +++ b/js/index.md @@ -162,6 +162,7 @@ groups: - iterator - iterator-take - iterator-to-array + - iterator-filter - name: "Обработка исключений" items: - try-catch diff --git a/js/iterator-filter/index.md b/js/iterator-filter/index.md new file mode 100644 index 0000000000..12bc7e0c76 --- /dev/null +++ b/js/iterator-filter/index.md @@ -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 +```