Урок 7 из 8 · 25 мин · обновлено

Неизменяемые методы массивов и callback

Что такое callback и как с его помощью работают методы map, filter, find и reduce, которые не меняют исходный массив. И как правильно сортировать числа.

Функция как значение

В JavaScript функцию можно положить в переменную и передать в другую функцию, как число или строку:

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: сделать что-то с каждым элементом

JavaScript
const prices = [450, 90, 1200];
prices.forEach((price) => console.log(`${price} ₽`));

forEach ничего не возвращает — он просто вызывает callback для каждого элемента. По сути, это for...of в виде метода.

map: превратить каждый элемент

JavaScript
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: оставить подходящие

JavaScript
const expensive = prices.filter((price) => price > 100);
console.log(expensive);   // [450, 1200]

Callback должен вернуть true или false: элемент попадёт в новый массив, если вернулось true.

find: найти первый подходящий

JavaScript
const firstCheap = prices.find((price) => price < 100);
console.log(firstCheap);   // 90

Если ничего не нашлось, find вернёт undefined.

reduce: свернуть в одно значение

reduce проходит по массиву и накапливает результат — например, сумму:

JavaScript
const total = prices.reduce((sum, price) => sum + price, 0);
console.log(total);   // 1740

Callback получает два значения: накопленное (sum) и текущий элемент (price). Второй аргумент reduce — начальное значение, здесь 0. Это та же логика, что и с циклом из урока 3: «завести переменную и прибавлять на каждом шаге», только короче.

Цепочки методов

Раз методы возвращают новые массивы, их можно вызывать друг за другом:

JavaScript
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, который сравнивает два элемента:

JavaScript
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 баллов; и средний балл всех студентов.

Решение
JavaScript
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 остаётся прежним — его можно спокойно использовать дальше.

Мини-тест: проверьте себя

Закрепите урок: несколько вопросов, разбор ответов — в конце.

  1. Что такое callback?

  2. Что вернёт [1, 2, 3].map((n) => n * 10)?

  3. Что вернёт [450, 90, 1200].filter((p) => p > 100)?

  4. Как отсортировать числа по возрастанию?