Такие методы, как 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.

Первый параметр  —  это функция обратного вызова, которая выполняется для каждого элемента массива и должна возвращать истинное значение для сохранения элемента в результирующем новом массиве и ложное значение в противном случае.

Функция обратного вызова принимает следующие аргументы:

Посмотрим, что это за параметры.

  1. element  —  текущий элемент массива, для которого вызывается метод filter.
  2. indexOfElement  —  индекс текущего элемента, который обрабатывается функцией обратного вызова.
  3. 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 с помощью пользовательской функции, принимающей два аргумента:

  1. Функцию обратного вызова.
  2. 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

Предыдущая статьяХуки Mongoose: все, что нужно знать
Следующая статьяSwift: ссылочные типы и циклы сохранения, weak и unowned