Рассмотрим следующую задачу. Возьмём список пользователей и извлечём из него имена всех пользователей:
const users = [
{ name: 'Igor', age: 19 },
{ name: 'Danil', age: 1 },
{ name: 'Vovan', age: 4 },
{ name: 'Matvey', age: 16 },
]
const result = []
for (const user of users) {
result.push(user.name)
}
console.log(result) // => ['Igor', 'Danil', 'Vovan', 'Matvey']
Здесь мы видим обычную агрегацию с использованием цикла for...of. А что, если нам понадобится извлечь возраст? Повторяем:
const result = []
// Добавили деструктуризацию
for (const { age } of users) {
result.push(age)
}
console.log(result) // => [19, 1, 4, 16]
В примерах выше легко увидеть закономерность. Выполняется один и тот же проход по циклу, и результат собирается в массив result
. Единственное, что меняется — значение, которое мы извлекаем из элементов исходного массива.
Операция, которую мы выполняли в обеих ситуациях, называется отображением (mapping). В коде мы взяли исходный массив и отобразили его в другой массив, попутно выполнив необходимые преобразования над каждым элементом. Важно, что размер получившегося массива равен размеру исходного массива.
Задача отображать данные в реальном коде встречается буквально на каждом шагу. Это настолько важная операция, что для нее создана специальная функция высшего порядка map():
const names = users.map(user => user.name)
console.log(names) // => ['Igor', 'Danil', 'Vovan', 'Matvey']
const ages = users.map(user => user.age)
console.log(ages) // => [19, 1, 4, 16]
// Или что то же самое
const callback = user => user.age
console.log(users.map(callback)) // => [19, 1, 4, 16]
Метод map()
принимает первым параметром функцию. Дальше, внутри себя, map()
перебирает элементы переданной коллекции и для каждого элемента вызывает переданную функцию. На вход этой функции передаётся элемент исходного массива, а её результат записывается в новый массив, который и возвращается наружу.
Сравните решение задачи получения списка имен через цикл и с помощью метода map()
. Последний имеет довольно много преимуществ. Во-первых, код с ним значительно короче. Во-вторых, от нас скрыта повторяющаяся логика перебора. Больше не нужно явно определять цикл и выполнять руками все те операции, которые можно не выполнять. Метод map()
позволяет сосредоточиться на сути происходящего, скрывая ненужные детали (проход по циклу).
Типичный пример, который любят приводить в документации к функции map()
разных языков программирования — применение некоторой арифметической операции к каждому элементу коллекции:
const numbers = [5, 2, 3]
const newNumbers = numbers.map(number => number ** 2)
console.log(newNumbers) // => [25, 4, 9]
const newNumbers2 = numbers.map(number => number + 3)
console.log(newNumbers2) // => [8, 5, 6]
Пример выглядит искусственно, но хорошо отражает суть операции.
Реализация
Напишем свою собственную функцию myMap()
, работающую аналогично методу массива map()
:
const myMap = (collection, callback) => {
const result = []
for (const item of collection) {
// Вызов переданного колбека на каждом элементе коллекции
const newItem = callback(item)
// Возврат из колбека добавляется в результирующий массив
result.push(newItem)
}
return result
}
const numbers = [5, 2, 3]
const newNumbers = myMap(numbers, number => number ** 2)
console.log(newNumbers) // => [25, 4, 9]
Главное отличие кода функции myMap()
(и метода map()
) от ручного обхода массива заключается в том, что функция myMap()
не знает, что нужно сделать с каждым элементом массива. Поэтому она принимает вторым аргументом функцию, которую вызывает для каждого элемента исходного массива, а результат вызова записывается в выходной массив. Чем будет этот результат — функция myMap()
не знает, и ей этого знать не нужно. Ответственность за обработку лежит на пользователях.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.