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