Что такое разреженный массив — и почему map не заполняет Array(3)

Хочешь сетку из трёх нулей. Пишешь new Array(3).map(() => 0). Выводишь в консоль — а там [ <3 empty items> ]. Ни одного нуля. Ошибки нет. map просто… ничего не сделал.
Смотри, тут хитрая штука. Массив на три места — это не три пустых значения. Это три дырки. А дырки map пропускает. Такой массив называется разреженным — и он ломает больше кода, чем кажется.
Что такое разреженный массив
Разреженный массив (sparse array) — это массив, в котором у некоторых индексов вообще нет элемента. Не undefined, не null — именно ничего.
Обычный массив — как ряд коробок, где в каждой что-то лежит. Разреженный — ряд, где некоторых коробок нет. Но номера на полке при этом остались.
Дырки появляются тремя путями:
new Array(3) // [ <3 empty items> ] — три места, ноль элементов
const a = []; a[100] = 1 // длина 101, а элемент один
delete list[1] // удалил — и оставил дырку на месте
Есть и четвёртый, по опечатке: две запятые подряд. [1, , 3] — это массив с дыркой посередине.
И два незаметных. arr.length = 10 растягивает короткий массив — новые места становятся дырками. А arr[arr.length + 1] = x вместо arr.push(x) — ошибка на единицу, которая оставляет одну дырку перед новым элементом. Добавляешь в конец — пиши push, он дырок не делает.
Как ведут себя дырки — главное, что надо запомнить
Разные команды видят дырку по-разному. Поэтому баги такие загадочные:
const c = [1, , 3]
c.length // 3 — длина считает дырку
c.forEach(x => ...) // вызовется 2 раза, дырку пропустит
c.map(x => x * 2) // [2, <1 empty item>, 6] — дырка осталась
c.filter(() => true) // [1, 3] — дырку выкинул
[...c] // [1, undefined, 3] — дырка стала undefined
JSON.stringify(c) // "[1,null,3]" — а тут уже null
Вот где кроется сюрприз. c.includes(undefined) скажет true. А c.indexOf(undefined) скажет -1. Один и тот же вопрос — «есть ли тут undefined?» — и два разных ответа.
И ещё: обычный цикл for (let i = 0; i < c.length; i++) дырку не пропускает. Он честно отдаст undefined. Так что forEach и for на одном массиве могут посчитать разное. Подробнее про разницу циклов — в статье цикл или map.
Почему это важно тебе
На практике дырки бьют в трёх местах.
Длина врёт. Ты положил товар в cart[100], потому что id товара 100. Теперь cart.length равно 101. Счётчик «В корзине: 101 товар» на экране. Хотя товар один. Когда ключи — это id, а не порядковые номера, нужен объект, а не массив.
Пустая сетка не заполняется. Тот самый Array(3).map(...). Так строят доску для игры, календарь, таблицу — и получают пустоту.
JSON меняет смысл. Сохранил массив с дыркой на сервер — вернулся null. А null уже не дырка, это значение. Код, который проверял «есть ли элемент», вдруг видит, что есть. Про то, чем null отличается от «ничего», — в статье что такое null.
Есть и скрытая цена. Движок V8 (Chrome, Node.js) хранит массивы с дырками в менее быстром режиме. И обратно в быстрый такой массив уже не вернётся, даже если дырки заполнить. На маленьких массивах ты этого не заметишь. На миллионе элементов — заметишь.
Как сделать массив без дырок
Вместо new Array(n) используй Array.from:
Array.from({ length: 3 }, () => 0) // [0, 0, 0]
Array.from({ length: 5 }, (_, i) => i) // [0, 1, 2, 3, 4]
Короткий вариант — new Array(3).fill(0). fill заполняет дырки, и дальше map работает. Но с fill есть своя ловушка: если положить туда [] или {}, все ячейки получат один и тот же объект. Для сеток из массивов надёжнее Array.from с функцией.
Удаляешь элемент? Не delete, а splice или filter:
list.splice(1, 1) // вырезал, индексы сдвинулись
list = list.filter(x => x !== item) // новый массив без элемента
И если ИИ-ассистент написал тебе new Array(n).map(...) — это почти наверняка баг. Попроси переписать через Array.from.
Как увидеть дырку в консоли?
Chrome пишет empty × 3, Node.js — <3 empty items>. Проверить конкретный индекс можно так: 1 in arr. Для дырки будет false, а для настоящего undefined — true.
Дырка и undefined — это не одно и то же?
Нет. [undefined] — массив с одним элементом, у которого значение undefined. [ , ] — массив без элементов вообще. forEach по первому пройдёт один раз, по второму — ни разу.
Бывают ли разреженные массивы в Python?
Нет. Список в Python нельзя «растянуть» присваиванием: lst[100] = 1 на коротком списке упадёт с IndexError. Дырки — особенность JavaScript.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





