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

Введение

Как мы видели в нашей реализации map, создание подмножества массива - очень распространенная задача в JavaScript. С map мы трансформируем каждый элемент в массиве. С filter мы получаем подмножество массива на основе некоторого условия.

Это четвертая статья из серии, в которой я объясняю встроенные методы массивов JavaScript, уделяя особое внимание тому, как они работают и как создавать свои собственные. За 4 года, которые я потратил на обучение программированию сотен людей, я обнаружил, что размышления о том, как работают эти методы массивов, в конечном итоге заставят их щелкнуть по людям, изучающим JavaScript. filter - это мощный метод, который стоит иметь в вашем наборе инструментов, поэтому давайте посмотрим, как он работает.

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

Метод filter принимает обратный вызов и вызывает его для каждого элемента в массиве. Затем он проверяет, является ли значение, возвращаемое обратным вызовом, правдой. Если это так, то он добавит значение в новый массив. Если возвращаемое значение не соответствует действительности, это значение пропускается. В конце концов, у нас остались все значения, которые вернули истину при передаче в обратный вызов.

В качестве простого примера возьмем все четные числа из массива. В JavaScript мы знаем, что число есть, даже если остаток от деления его на два равен 0 (с использованием оператора по модулю):

// Even numbers
let num = 6
if (num % 2 === 0) {
	console.log(num + " is even!")
}

Мы можем использовать это внутри функции, чтобы проверить, является ли аргумент четным числом:

function isEven(num) {
	if (num % 2 === 0) {
		return true
	} else {
		return false
	}
}
isEven(6) // true
isEven(7) // false

Теперь, когда у нас есть функция, которая проверяет, является ли число четным или нечетным, мы можем использовать ее для проверки каждого числа в массиве, передав его в .filter():

let nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
let evens = nums.filter(isEven)
console.log(evens) // [ 2, 4, 6, 8, 10 ]

Метод .filter() принимает нашу isEven функцию (как обратный вызов) и вызывает ее для каждого элемента в массиве. Если isEven возвращает true, мы добавляем текущий элемент в новый массив; если он возвращает false, то мы пропускаем текущий элемент. Когда filter() завершает цикл по массиву nums, он возвращает новый массив всех элементов, которые вернули true при передаче в isEven.

Это, наверное, немного сложно уследить. Поэтому вместо того, чтобы объяснять это подробнее, давайте реализуем нашу собственную. Просмотр кода поможет вам понять, что filter делает лучше, чем что-либо другое.

Реализация собственных

Чтобы реализовать нашу собственную версию метода filter, нам нужно определить функцию, которая выполняет следующие действия:

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

Мы рассмотрим это шаг за шагом, начиная с создания функции с именем filter, которая принимает в качестве аргументов массив и обратный вызов:

function filter(items, cb) {
	// More to come here ...
}

Наш первый аргумент, items, будет переданным в массиве. Второй элемент, cb, будет обратным вызовом. (Кстати, cb - это обычное сокращение для обратного вызова в JavaScript.)

Затем нам нужно создать пустой массив, который мы вернем в конце функции:

function filter(items, cb) {
	let final = []
	// More to come here ...
	return final
}

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

Теперь, когда у нас есть массив final, нам нужно создать цикл, который будет проходить через каждый элемент в массиве items. Мы будем использовать этот вид, чтобы передать каждый элемент массива items в наш обратный вызов cb.

function filter(items, cb) {
	let final = []
	for (let i = 0; i < items.length; i++) {
		// More to come here ...
	}
	return final
}

В этом цикле мы хотим вызвать нашу функцию обратного вызова (cb) для текущего элемента в цикле:

function filter(items, cb) {
	let final = []
	for (let i = 0; i < items.length; i++) {
		// Get the current item
		let currentItem = items[i]
		// Pass it into `cb`
		let res = cb(currentItem)
		// More to come here ...
	}
	return final
}

Теперь мы получаем текущий элемент из массива items (на основе значения индекса i) и передаем его в обратный вызов (cb). Мы фиксируем возвращаемое значение cb в переменной res. Вы также можете назвать эту переменную как угодно, хотя res здесь тоже довольно распространен (это сокращение от result).

Мы почти закончили. Осталось только проверить возвращаемое значение (res), чтобы убедиться, что оно соответствует действительности. Если это так, мы добавим его в массив final, а если неверно, пропустим:

function filter(items, cb) {
	let final = []
	for (let i = 0; i < items.length; i++) {
		let currentItem = items[i]
		let res = cb(currentItem)
		if (res) {
			final.push(currentItem)
		}
	}
	return final
}

Вот и все! Это все, что нам нужно сделать. Нам не нужен else, потому что мы не хотим ничего делать, если res ложно. Нам нечего было бы вставлять в else блок!

Мы можем протестировать наш собственный filter метод, используя функцию isEven, которую мы определили выше:

let nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
let evens = filter(nums, isEven)
console.log(evens) // [ 2, 4, 6, 8, 10 ]

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

function isOdd(num) {
	if (num % 2 !== 0) {
		return true
	} else {
		return false
	}
}
let odds = filter(nums, isOdd)
console.log(odds) // [ 1, 3, 5, 7, 9 ]

Вы можете видеть здесь, что это также работает с нашей функцией isOdds.

filter в действии

Метод filter - лучший способ получить подмножество списка, что является довольно распространенной задачей при разработке JavaScript. Одно из мест, где я его часто использую, - это получение раздела ответа API.

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

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

Если данные, которые мы получаем от API, выглядят так:

const developers = [
  {
    name: "Antonio",
    experience: "Junior",
    language: "JavaScript"
  },
  {
    name: "José",
    experience: "Senior",
    language: "JavaScript"
  },
    {
    name: "Carmen",
    experience: "Junior",
    language: "Python"
  },
    {
    name: "Mercedes",
    experience: "Junior",
    language: "JavaScript"
  },
    {
    name: "Ana",
    experience: "Junior",
    language: "JavaScript"
  },
]

Затем мы можем отфильтровать всех младших разработчиков примерно так:

function isJunior(developer) {
	if (developer.experience === "Junior") {
		return true
	}
	
	return false
}
const juniorDevelopers = developers.filter(isJunior)

Это даст нам Антонио, Кармен, Мерседес и Ану.

Мы могли бы сделать нечто подобное для разработчиков JavaScript:

function knowsJavaScript(developer) {
	if (developer.language === "JavaScript") {
		return true
	}
	return false
}
const javascriptDevelopers = developers.filter(knowsJavaScript)

В нашем массиве javascriptDevelopers теперь есть Антонио, Хосе, Мерседес и Ана.

И если мы хотим filter для младших разработчиков и разработчиков JavaScript, мы тоже можем это сделать:

const currentDevelopers = developers.filter(function (developer) {
	if (
		developer.language === "JavaScript" &&
		developer.experience === "Junior
	) {
		return true
	}
	return false
})

Все, что осталось сделать, чтобы создать нашу страницу в приложении для отслеживания кандидатов, - это отобразить этот массив разработчиков на экране!

Вывод

Я рассмотрел четыре встроенных метода массива JavaScript. Если вы еще этого не сделали, обязательно ознакомьтесь с другими статьями из этой серии:

Я собираюсь пройти, объяснить и реализовать каждый встроенный метод массива. Если вы изо всех сил пытаетесь их изучить или просто хотите узнать больше о JavaScript в целом, не забудьте подписаться на мою рассылку новостей и подписаться на меня в Twitter.