Такие методы, как Array.prototype.filter(), Array.prototype.map() и Array.prototype.reduce(), являются неотъемлемой частью парадигмы функционального программирования JavaScript. Но достаточно ли хорошо вы понимаете, как работает метод filter? Не будем вдаваться в подробности реализации, а рассмотрим только внутренний механизм этого метода. “Дьявол кроется в деталях” — попробуем копнуть поглубже и поискать этого “дьявола” в языке JavaScript.
В JavaScript массивы не являются “примитивными значениями”, а рассматриваются как “объекты”, а при слове “объект” на ум приходит другое слово — “метод”. В этой статье хочу обратить ваше внимание на неизменяющие (Non-Mutating ) и изменяющие (Mutating) методы массива. На простом языке разницу между ними я бы объяснил так: методы, которые не изменяют исходный массив, а создают или возвращают новый массив с необходимыми изменениями, называются неизменяющими, а изменяющие методы — это те, которые изменяют исходный массив.
Array.prototype.filter() — один из неизменяющих методов, возвращающий новый массив. Возвращаемый массив содержит элементы, отфильтрованные из исходного массива на основе теста, реализованного функцией обратного вызова, переданной в качестве входного аргумента методу .filter().
Сигнатура метода “filter” выглядит следующим образом:
Как видите, у нас есть два параметра — callbackfn и thisArg (thisArg — опциональный параметр).
Обсудим первый параметр, т. е. callbackfn.
Первый параметр — это функция обратного вызова, которая выполняется для каждого элемента массива и должна возвращать истинное значение для сохранения элемента в результирующем новом массиве и ложное значение в противном случае.
Функция обратного вызова принимает следующие аргументы:
Посмотрим, что это за параметры.
- element — текущий элемент массива, для которого вызывается метод filter.
- indexOfElement — индекс текущего элемента, который обрабатывается функцией обратного вызова.
- array — массив, для которого вызывается метод filter.
Теперь, когда вы поняли сигнатуру метода filter (сигнатура метода — это информация о передаваемых в него параметрах), рассмотрим вкратце реализацию метода .filter:
Здесь я пытаюсь отфильтровать четные числа из массива, обозначенного как “numbers”. Поэтому вызвал метод filter на массиве “numbers” и передал одну функцию обратного вызова, обозначенную как “filterEven”. Эта функция обратного вызова выполняется для каждого элемента массива и проверяет условие. В данном случае условием является num % 2 === 0.
Таким образом, элемент, который пройдет проверку, получит “пропуск” в новый массив, а тот, который не пройдет, никогда не попадет в массив, возвращенный методом filter. Вот так работает метод filter.
Зачем использовать метод Array.prototype.filter()? Разве нельзя добиться фильтрации с помощью forEach или чего-то подобного?
Отфильтруем четные числа из массива с помощью forEach:
Разберем, что я сделал в приведенном выше фрагменте кода.
Определил пустой массив как “filteredArray”, затем вызвал метод forEach для массива “numbers” и передал в forEach одну функцию обратного вызова “filterEven”, которая будет выполняться для каждого элемента массива “numbers”. Функция обратного вызова содержит один условный оператор “if”, который проверяет, является ли число четным, и если да, то заносит его в новый массив, обозначенный как “filteredArray”.
Зачем же использовать метод filter даже тогда, когда можно добиться такой же фильтрации с помощью метода forEach? Выбор метода filter объясняется его преимуществами:
- Надежность и выразительность. Метод filter определяется специально для фильтрации элементов по условию, заданному в функции обратного вызова, он четко передает замысел фильтрации другим разработчикам, что облегчает сопровождение кода.
- Лаконичность. Метод filter намного лаконичнее, чем forEach.
- Неизменяемость. Метод filter возвращает новый массив, содержащий только те элементы, которые прошли заданный тест, что способствует неизменяемости — одному из ключевых принципов функционального программирования. Это означает, что вы не изменяете исходный массив, а создаете новый массив с нужными элементами.
- Образование цепочки. Поскольку метод filter возвращает новый массив, его можно легко объединить в цепочку с другими методами работы с массивами, такими как map, reduce и forEach, чтобы выполнить последовательность операций более элегантным и лаконичным способом.
Теперь пришло время посмотреть, как работает метод Array.prototype.filter().
В приведенном выше фрагменте кода показан один из способов создания метода filter().
Попробую упростить ситуацию, разобрав код построчно:
Это условие — другой способ сказать if(true).
Здесь переопределяем метод Array.prototype.filter с помощью пользовательской функции, принимающей два аргумента:
- Функцию обратного вызова.
- thisArg → thisArg — массив, для которого будет выполняться метод filter. Допустим, имеется массив с именем “numbers “, и вы запускаете на нем метод filter(), тогда это будет выглядеть примерно так: “numbers.filter()”, поэтому в данном случае “thisArg” будет равнозначен “numbers”.
Убедимся в том, что не используем по умолчанию метод filter, заданный JavaScript.
Здесь “this” будет массивом, для которого мы вызываем метод filter. Удостоверимся в том, что массив не null или undefined. В противном случае выбрасывается новая ошибка TypeError.
В этой строке удостоверяемся в том, что передаваемый нами обратный вызов является функцией и ничем другим.
Создание пустого массива, который будет возвращен методом filter:
Здесь “this” — массив, для которого будет выполняться метод filter. Как было сказано ранее, метод forEach выполняется для массива, где forEach принимает в качестве входного аргумента функцию обратного вызова с именем “processEachElement”. Рассмотрим эту функцию обратного вызова, названную “processEachElement”:
Это та же самая функция, которую мы использовали в качестве обратного вызова в методе forEach. Функция принимает три входных параметра, которые не требуют пояснений. Но хочу обратить ваше внимание на условный оператор “if” внутри функции.
Здесь мы вызываем функцию обратного вызова (которую передали в качестве входного аргумента методу filter) методом “call”, связываем “this” функции обратного вызова с “thisArg” (это массив, для которого запускается метод filter), после параметра “thisArg” передаем те же параметры, которые передаются в функцию обратного вызова forEach “processEachElemet”.
Функция обратного вызова возвращает истинные или ложные значения. Если возвращаемое значение функции обратного вызова (в условном операторе “if”) равно true, помещаем элемент в массив filteredArray.
И, наконец, возвращаем отфильтрованный массив.
Вот как можно создать функцию filter:
Читайте также:
Читайте нас в Telegram, VK и Дзен
Перевод статьи Prathm: The Art of Array Manipulation: Exploring JavaScript’s Array.prototype.filter() method