Что такое…

Что такое разреженный массив — и почему 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.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →