JavaScript / 40 минут

Массивы и преобразования

Используйте map, filter и reduce для обработки коллекций.

В процессе Теория: впереди Практика: впереди Тест: впереди
08 / 22
После этой главы
  • Используйте map, filter и reduce для обработки коллекций.
  • Применить тему в задаче «Массивы и преобразования» и объяснить граничные случаи.

Около 10 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Массив как упорядоченная коллекция

Массив хранит последовательность элементов с числовыми индексами, начинающимися с нуля. Свойство length связано с диапазоном индексов, а не обязательно с числом реально заполненных позиций: в JavaScript возможны разреженные массивы. Для обычных учебных данных лучше создавать плотные последовательности и не удалять элементы оператором delete. Если нужно убрать элемент со сдвигом остальных, выбирают подходящую операцию над массивом.

Последний индекс обычного непустого массива равен length - 1. Чтение отсутствующей позиции даёт undefined и не бросает исключение само по себе. Поэтому после поиска или индексирования иногда нужна отдельная проверка. Массив является объектом: две отдельные последовательности с одинаковыми элементами не равны через ===. Сравнение содержимого определяется требованиями задачи, а не самим фактом использования квадратных скобок.

JavaScript
const courses = ['JS', 'TS', 'Express'];
console.log(courses[0]); // JS
console.log(courses.at(-1)); // Express
console.log(courses[10]); // undefined

Изменяющие операции и копирование

push добавляет элемент в конец, pop удаляет последний, shift и unshift работают с началом. splice изменяет содержимое на месте. slice создаёт новый массив из выбранного диапазона. sort по умолчанию тоже изменяет исходный массив и сравнивает строковые представления; для числовой сортировки нужен comparator. Современный toSorted возвращает новую отсортированную последовательность, если доступен в целевой среде.

JavaScript
const original = [10, 2, 30];
const sorted = [...original].sort((a, b) => a - b);
console.log(sorted); // [2, 10, 30]
console.log(original); // [10, 2, 30]

Spread и slice копируют саму последовательность ссылок, но не делают глубокую копию вложенных объектов. Если поменять свойство объекта внутри копии, исходный массив продолжит ссылаться на тот же объект. Это важно для состояния интерфейса: новый внешний массив не гарантирует независимости вложенных данных. Выбирайте глубину копирования по конкретной структуре и смыслу изменения.

map и filter: разные вопросы

map создаёт новый массив результатов callback. Его длина для обычного плотного массива соответствует длине исходного. filter сохраняет только элементы, для которых callback вернул истинное значение. map отвечает на вопрос «во что превратить каждый элемент», filter — «какие элементы оставить». Если использовать map ради побочного эффекта и проигнорировать возвращаемый массив, намерение кода становится менее очевидным.

JavaScript
const scores = [10, -2, 7, 0];
const positive = scores.filter(score => score > 0);
const doubled = positive.map(score => score * 2);
console.log(doubled); // [20, 14]

В этом примере отрицательные значения и ноль удаляются намеренно по условию > 0. Если бы требовались неотрицательные, правило изменилось бы на >= 0. Предсказывайте состав промежуточного массива отдельно от итогового: так легче обнаружить ошибку в фильтрации, которая иначе выглядит как неправильная арифметика на следующем шаге.

reduce и начальное значение

reduce сворачивает последовательность в один результат: сумму, словарь или другую структуру. Callback получает аккумулятор и текущий элемент. Начальное значение определяет тип и поведение пустого входа. Без него reduce берёт первый элемент как старт и бросает ошибку для пустого массива. Поэтому в расчёте суммы передавайте 0 явно.

JavaScript
const prices = [100, 250, 50];
const total = prices.reduce((sum, price) => sum + price, 0);
const emptyTotal = [].reduce((sum, price) => sum + price, 0);
console.log(total, emptyTotal); // 400 0

Не превращайте reduce в обязательный стиль для любого цикла. Если аккумулятор сложный, состояние обновляется в нескольких ветвях или важен ранний выход, обычный for...of может быть понятнее. Краткость записи не является самостоятельным доказательством качества. Выбирайте конструкцию, в которой легче увидеть инвариант и проверить граничные случаи.

Поиск, проверки и раннее завершение

find возвращает первый подходящий элемент или undefined. findIndex возвращает индекс или -1. some проверяет наличие хотя бы одного подходящего элемента, every — соответствие всех. Для пустого массива every возвращает true, а some — false: это следует из смысла «нет нарушающего элемента» и «нет найденного подходящего элемента». Учитывайте это, когда бизнес-правило требует ещё и непустого набора.

JavaScript
const tasks = [{ done: true }, { done: false }];
console.log(tasks.some(task => task.done)); // true
console.log(tasks.every(task => task.done)); // false
console.log(tasks.find(task => !task.done)); // второй объект

Разбор задачи и контроль изменений

В задании требуется обработать массив по заданному правилу отбора и преобразования. Сначала запишите, какие значения должны остаться после фильтра, затем посчитайте ожидаемый результат вручную. Пустой массив — полноценный вход, а не случай, который можно пропустить. Ноль и отрицательные числа проверяют границы правила; повторяющиеся значения проверяют, не удалили ли вы дубликаты без требования.

После вызова сравните исходный массив с его состоянием до вычисления. Если условие не разрешает мутацию, методы sort и splice на исходном значении могут нарушить контракт даже при правильном числовом ответе. Для самостоятельной практики решите ту же задачу через for...of и через цепочку filter/map/reduce, а затем объясните, почему оба варианта имеют одинаковое наблюдаемое поведение.

Загружаем редактор…

Музыка: Reverie — Scott Buckley · CC BY 4.0