Урок 7 из 8 · 25 мин · обновлено
Неизменяемые методы массивов и callback
Что такое callback и как с его помощью работают методы map, filter, find и reduce, которые не меняют исходный массив. И как правильно сортировать числа.
Функция как значение
В JavaScript функцию можно положить в переменную и передать в другую функцию, как число или строку:
const sayHi = (name) => console.log(`Привет, ${name}!`);
function greetAll(names, action) {
for (const name of names) {
action(name); // вызываем переданную функцию
}
}
greetAll(["Анна", "Иван"], sayHi);
// Привет, Анна!
// Привет, Иван!
Функция, которую передают в другую функцию, чтобы та вызвала её сама, называется callback (от английского «обратный вызов»). Здесь sayHi — callback: мы не вызываем её сами, это делает greetAll для каждого имени.
На callback построены самые полезные методы массивов.
Неизменяемые методы
В отличие от методов из прошлого урока, эти не меняют исходный массив. Они возвращают новый массив или значение, а исходные данные остаются нетронутыми. Это безопаснее: никакая другая часть программы не получит неожиданно изменённые данные.
forEach: сделать что-то с каждым элементом
const prices = [450, 90, 1200];
prices.forEach((price) => console.log(`${price} ₽`));
forEach ничего не возвращает — он просто вызывает callback для каждого элемента. По сути, это for...of в виде метода.
map: превратить каждый элемент
const prices = [450, 90, 1200];
const withVat = prices.map((price) => price * 1.2);
console.log(withVat); // [540, 108, 1440]
console.log(prices); // [450, 90, 1200] — исходный не изменился
map вызывает callback для каждого элемента и собирает результаты в новый массив той же длины.
filter: оставить подходящие
const expensive = prices.filter((price) => price > 100);
console.log(expensive); // [450, 1200]
Callback должен вернуть true или false: элемент попадёт в новый массив, если вернулось true.
find: найти первый подходящий
const firstCheap = prices.find((price) => price < 100);
console.log(firstCheap); // 90
Если ничего не нашлось, find вернёт undefined.
reduce: свернуть в одно значение
reduce проходит по массиву и накапливает результат — например, сумму:
const total = prices.reduce((sum, price) => sum + price, 0);
console.log(total); // 1740
Callback получает два значения: накопленное (sum) и текущий элемент (price). Второй аргумент reduce — начальное значение, здесь 0. Это та же логика, что и с циклом из урока 3: «завести переменную и прибавлять на каждом шаге», только короче.
Цепочки методов
Раз методы возвращают новые массивы, их можно вызывать друг за другом:
const prices = [450, 90, 1200, 300];
const total = prices
.filter((price) => price >= 300) // [450, 1200, 300]
.map((price) => price * 0.9) // скидка 10%
.reduce((sum, price) => sum + price, 0);
console.log(total); // 1755
Код читается как описание задачи: «возьми цены от 300, примени скидку, сложи».
Как правильно сортировать числа
Помните ловушку с sort()? Чтобы сортировать числа, методу передают callback, который сравнивает два элемента:
const prices = [450, 1200, 90];
prices.sort((a, b) => a - b); // по возрастанию: [90, 450, 1200]
prices.sort((a, b) => b - a); // по убыванию: [1200, 450, 90]
Если результат callback отрицательный, a встаёт раньше b, если положительный — позже. sort по-прежнему мутирующий метод: он меняет исходный массив.
Попробуйте сами. Есть оценки студентов const scores = [72, 95, 58, 88, 64]. Получите: массив оценок от 70 и выше; эти же оценки, увеличенные на 5 баллов; и средний балл всех студентов.
Решение
const scores = [72, 95, 58, 88, 64];
const passed = scores.filter((s) => s >= 70); // [72, 95, 88]
const boosted = passed.map((s) => s + 5); // [77, 100, 93]
const sum = scores.reduce((acc, s) => acc + s, 0); // 377
const average = sum / scores.length; // 75.4
console.log(passed, boosted, average);
Каждый шаг даёт новый массив, а исходный scores остаётся прежним — его можно спокойно использовать дальше.
Мини-тест: проверьте себя
Закрепите урок: несколько вопросов, разбор ответов — в конце.
-
Что такое callback?
В map(), filter() и других методах вы передаёте функцию, а метод сам вызывает её для каждого элемента. Такая функция и называется callback.
-
Что вернёт [1, 2, 3].map((n) => n * 10)?
map() вызывает callback для каждого элемента и собирает результаты в новый массив той же длины. Исходный массив не меняется.
-
Что вернёт [450, 90, 1200].filter((p) => p > 100)?
filter() оставляет только те элементы, для которых callback вернул true. 90 условие не прошло.
-
Как отсортировать числа по возрастанию?
Без callback sort() сравнивает элементы как строки. Функция (a, b) => a - b говорит методу сравнивать их как числа.