Что выведет консоль в случае присвоения свойства массиву по строковому индексу?

Дан код:

const a = [1, 2, 4];
a["7"] = 3;

console.log(a); // Первый
console.log(a.length); // Второй
console.log(a.map((item) => item)); // Третий
a.forEach((item) => console.log(item)); // Четвёртый

Что выведетcя в console.log-ах?

Теория: индексы и length

В JavaScript массив является объектом Array, а элементы хранятся как свойства, ключи которых выглядят как индексы (например, "0", "1", "7").
Свойство length у массива автоматически связано с максимальным индексом: если установить элемент на индексе k, то length становится как минимум k + 1.

Запись a["7"] = 3 использует ключ "7". Поскольку это строковая форма неотрицательного целого числа, такой ключ трактуется как индекс массива, а не как «обычное» строковое свойство. В результате:

  • появляется элемент на позиции 7,
  • length увеличивается до 8,
  • между старыми элементами (0–2) и новым элементом (7) образуются пустые слоты (3–6).
Массив в JavaScript не является «ассоциативным массивом»: произвольные строковые ключи могут стать обычными свойствами объекта массива и не участвовать в стандартных обходах элементов. Важно различать ключ "7" (индекс) и, например, ключ "foo" (обычное свойство).

Схема массива после a["7"] = 3 (индексы 0..7):

- `0 → 1`
- `1 → 2`
- `2 → 4`
- `3 → (пусто)`
- `4 → (пусто)`
- `5 → (пусто)`
- `6 → (пусто)`
- `7 → 3`

Теория: пустые слоты (holes) и undefined

«Пустой слот» означает, что элемента на этом индексе не существует как свойства массива. При этом чтение a[3] всё равно вернёт undefined, потому что чтение несуществующего свойства объекта возвращает undefined.

Это принципиально отличается от ситуации, когда элемент существует и равен undefined. Тогда индекс считается «присутствующим», и многие методы обхода массива будут его посещать.

Пример отличия пустого слота от явного undefined:

const x = [];
x.length = 3;

console.log(x);        // [empty × 3]
console.log(x[1]);     // undefined (но элемента нет)

console.log(1 in x);   // false (индекса 1 как свойства нет)

const y = [undefined, undefined, undefined];
console.log(0 in y);   // true (свойство есть, значение undefined)

Здесь x[1] и y[1] оба читаются как undefined, но в x элемента нет, а в y элемент существует и хранит значение undefined.

Как именно сработает код

Исходный код:

const a = [1, 2, 4];
a["7"] = 3;

console.log(a); // Первый
console.log(a.length); // Второй
console.log(a.map((item) => item)); // Третий
a.forEach((item) => console.log(item)); // Четвёртый

Разбор по шагам:

  1. После const a = [1, 2, 4] массив имеет индексы 0..2 и length = 3.
  2. После a["7"] = 3 создаётся элемент на индексе 7, а length становится 8. Индексы 3, 4, 5, 6 остаются пустыми слотами (этих свойств нет).
  3. console.log(a) показывает разрежённый массив, обычно как [1, 2, 4, empty × 4, 3] (где «empty × 4» визуально означает отсутствующие элементы на 3–6).
  4. console.log(a.length) печатает 8.
  5. a.map((item) => item) создаёт новый массив той же длины 8, но колбэк вызывается только для существующих элементов (0, 1, 2, 7). Поэтому «дыры» не превращаются в undefined, а сохраняются как пустые слоты, и результат выглядит так же разрежённо.
  6. a.forEach(...) вызывает колбэк только для существующих элементов (0, 1, 2, 7). Поэтому в консоль попадут только 1, 2, 4, 3, а пустые слоты пропустятся полностью.

Ожидаемый смысл вывода:

  • Первый: [1, 2, 4, empty × 4, 3]
  • Второй: 8
  • Третий: [1, 2, 4, empty × 4, 3]
  • Четвёртый: 1, 2, 4, 3

Почему map и forEach пропускают «дыры»

Итеративные методы массива (включая map и forEach) работают не как «цикл от 0 до length-1 с чтением ai», а как обход существующих индексов. Поэтому для пустых слотов колбэк не вызывается вообще.

Следствия:

  • map не имеет возможности «подставить значение» для дыр, потому что для дыр колбэк не запускается; в новом массиве дырки сохраняются.
  • forEach не печатает undefined для дыр, потому что обход не доходит до отсутствующих элементов.
Если требуется именно пройтись по всем индексам от 0 до length - 1 (включая пустые слоты) и получить undefined при чтении отсутствующих элементов, то обычно применяется обычный цикл по индексу.

Пример обхода всех индексов, включая пустые слоты:

for (let i = 0; i < a.length; i++) {
  console.log(i, a[i]); // для дыр будет выводиться undefined, но индекс будет выведен
}

Таблица ожидаемого результата

ОперацияДлина/количествоЧто происходит с «дырами» 3..6Что видно в выводе
console.log(a)длина 8Дыры существуютОбычно показываются как empty/hole
console.log(a.length)8Дыры существуютПечатается число 8
a.map(item => item)длина 8Дыры сохраняютсяРезультат тоже разрежённый
a.forEach(...)4 вызова колбэкаДыры пропускаютсяПечатается 1, 2, 4, 3

Кратко: присваивание a["7"] = 3 создаёт элемент с индексом 7 и увеличивает length до 8, оставляя индексы 3–6 пустыми слотами; map сохраняет пустые слоты, а forEach пропускает их и выводит только существующие элементы.