Подробно про метод filter() в JavaScript

Stas Bagretsov

Недавно пользователь Artur Salikhov оставил комментарий, в котором написал, что нет достаточного количества информации про метод filter().

К сожалению, в интернете нет развернутых статей по этой теме. Так что в этой статье вы узнаете то, как работает вроде бы незамысловатый filter() в JavaScript и увидите разные вариации его глубокого применения.

Что это вообще за метод такой, filter()

Одной из самых распространенных задач при работе с массивами является создание нового массива, который содержит определенный набор данных из изначально заданного массива.

Предположим, что у нас есть массив из городов, каждый из которых представляет собой объект и содержит по два свойства: name и population.

const cities = [
{name: 'Moscow', population: 12506468},
{name: 'Saint Petersburg', population: 5351935},
{name: 'Novosibirsk', population: 1612833},
{name: 'Kaliningrad', population: 482443},
{name: 'Kaluga', population: 336726}
];

Чтобы найти города, население которых больше 1-го миллиона, вам обычно надо запускать цикл по всему массиву и тестировать значение свойства population на соответствие заданных условий, как например это показано в коде ниже:

const millionPlusCities = [];for (var i = 0; i < cities.length; i++) {
if (cities[i].population > 1000000) {
millionPlusCities.push(cities[i]);
}
}
console.log(millionPlusCities);

Получаем в консоль следующее:

Но к нашему счастью, в JavaScript есть такой удобный метод, как filter(), с помощью которого мы можем делать подобные операции без лишней писанины и куда более прозрачным для последующего восприятия кода способом.

Этот код выдаст такой же результат, как и пример выше:

const millionPlusCities = cities.filter(function (e) {
return e.population > 1000000;
});
console.log(millionPlusCities);

Мы вызываем метод filter() на массиве cities, состоящим из объектов и передаём функцию, которая отрабатывает на каждом элементе этого массива. Внутри функции мы проверяем, является ли значение population для каждого элемента выше, чем 1 миллион. Если да, то функция отдаёт true. В противном случае она отдаст false. Метод filter(), работая с финальным массивом, включит в него только элемент, на котором отработала функция и который подошел по всем условиям, выдав true.

Кстати, используя синтаксис ES6, можно сделать код чище, используя стрелочные функции (=>).

var millionPlusCities = cities.filter(e => e.population > 1000000);
console.log(millionPlusCities);

Подробно о методе filter()

Ниже описан синтаксис метода filter().

Array.filter(callback, contextObject);

Метод filter() создаёт новый массив, отфильтровывая все элементы, которые не проходят по условиям при выполнении callback() функции. Под капотом, filter() итерирует по каждому элементу в массиве и передаёт каждый из них самой callback() функции. Если callback() выдаёт true, то этот элемент будет включен в финальный массив.

В общем, метод filter() принимает два аргумента: функцию callback() и опцинально contextObject.

Как и с другими итеративными методами работы с массивами, такими как every(), some(), map() и forEach(), непосредственно callback() имеет следующую конструкцию:

function callback(currentElement, index, array){
// ...
}

Функция callback() берет 3 аргумента:

Аргумент currentElement это элемент массива, который в данный момент обрабатывается в callback() функции.

index это индекс данного, обрабатываемого элемента.

array это массив, по которому проходится функция.

Учтите, что аргументы index и array опциональны и в большинстве случаев вам просто не понадобятся в работе.

Также, опциональным является аргумент contextObject для метода filter(). Это объект, задающий контекст, передавая который, вы сможете ссылаться на него с помощью this уже внутри callback() функции.

Ещё очень важно отметить, что метод filter() не изменяет изначальный массив.

Ещё больше примеров метода filter()

Так как filter() отдаёт массив, вы можете связывать результаты выдачи в цепочке вызова методов, например итеративных, таких как sort() и map(). Для примера, давайте вместе свяжем 3 метода.

cities.filter(function (e) {
return e.population < 1000000;
}).sort(function (a, b) {
return b.population - a.population;
}).map(function (e) {
console.log(e.name + ': ' + e.population);
});

Получаем в консоль:

Kaliningrad: 482443
Kaluga: 336726

В ES6 это будет выглядеть так:

cities.filter(e => e.population < 1000000)
.sort((a, b) => (b.population - a.population))
.map(e => console.log(e.name + ': ' + e.population));

Как это работает:

Сначала метод filter() отдаёт список городов, население которых меньше 1-го миллиона человек.

Потом метод sort() делает сортировку в убывающем порядке, по результатам, которые отдал filter().

И под конец, метод map() выводит в консоль каждый элемент в получившемся массиве.

contextObject

В следующем примере вы увидите использование contextObject аргумента, который представляет собой объект, на который можно ссылаться в колбэке filter() через this.

function isInRange(value) {
if (typeof value !== 'number') {
return false;
}
return value >= this.lower && value <= this.upper;
}
const data = [10, 20, "30", 1, 5, 'JavaScript filter', undefined, 'example'];const range = {lower: 1, upper: 10};const numberInRange = data.filter(isInRange, range);console.log(numberInRange); // [10, 1, 5]

Как это работает.

Во первых, создаётся функция isInRange(), которая проверит является ли аргумент числом и находится ли указанное число в пределах указанных свойств lower и upper в contextObject.

Далее, создаётся массив из различных типов данных, в который включены числа, строки и undefined.

Потом создаём объект range, с двумя свойствами lower и upper.

И после всего этого, вызывается метод filter() на массиве data, с колбэком isInRange() и объектом range. Так как мы передаём объект range(), как contextObject, то внутри функции isInRange(), this будет ссылаться на этот самый объект.

И под конец, в консоль выводится получившийся массив.

В следующем примере мы увидим то, как можно отфильтровать массив слов по наличию нескольких букв.

const girls = ['Alena', 'Malena', 'Milena', 'Asya', 'Kasya'];const filterValues = (name) => {
return girls.filter(data => {
return data.toLowerCase().indexOf(name.toLowerCase()) > -1;
});
}
console.log(filterValues('le'));

Тут мы ищем строку, в которой есть подстрока ‘le’, если условие удовлетворено, то строка попадёт в итоговый массив. Вот, что мы получим на выходе.

["Alena", "Malena", "Milena"]

Зачастую метод filter() используют для того, чтобы исключить из массива: пустые строки, null и undefined (да и не только, как было показано в примере выше)

const arr = [0, null, 42, undefined, "", true, false, NaN, "", "foo bar"];
const filteredArr = arr.filter(function(val) {
return !(val === "" || typeof val == "undefined" || val === null);
});
// filteredArr = [0, 42, true, false, NaN, "foo bar"]

Ну и конечно же, всеми любимый аргумент Boolean, который уберет все false значения из массива, включая NaN, пустые строки и undefined.

const arr = [0, null, 42, undefined, "", true, false, NaN, "", "foo bar"];
const trueOnly = arr.filter(Boolean);
// trueOnly = [42, true, "foo bar"]

И последний пример, который учитывает все три аргумента для колбэк функции в filter().

let kgd = ['Kaliningrad', 'Koenigsberg', 'Selma', 'Ostmark'];
let pupersuper = [];
const modifiedWords = kgd.filter((word,index,array)=>{
array[index]+=' superpuper'
pupersuper.push(array[index]);
return word.length >= 11
});
pupersuper = ["Kaliningrad superpuper", "Koenigsberg superpuper", "Selma superpuper", "Ostmark superpuper"];modifiedWords = ["Kaliningrad", "Koenigsberg"];

Как вы видите, при каждой итерации, каждому элементу массива добавляется строка 'superpuper' и записывается в массив pupersuper с учётом индекса. Но само значение word будет оставаться неизменным, соответственно, в финальный массив попадут Kaliningrad и Koenigsberg.

Заключение

Метод filter() был представлен в ECMAScript 5, так что он недоступен для использования в старых брауpерах, таких как IE8 и т.п. Но если вам обязательно нужно использовать его для работы в них, то тогда вам придется воспользоваться компилятором babel или каким-нибудь из общедоступных полифилов. Ну или вы можете применить схожий по функционалу метод из таких библиотек, как Underscore или lodash.

Но сейчас это уже уж очень экзотический случай, так как поддержка ES5 уже 98.8%.

Этот блог бесплатный, в нём нет рекламы и ограничений paywall.
Вы можете его поддержать через Яндекс.Деньги. Спасибо.

Stas Bagretsov

Written by

Надеюсь верую вовеки не придет ко мне позорное благоразумие. webdev/sports/books

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade