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

Дан код:

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-ах? ƒ

Теория по задаче

Основная концепция: массивы как объекты

В JavaScript массивы — это особый тип объектов. Это фундаментальное понимание объясняет все неочевидные моменты в задаче.

typeof []; // "object"
Array.isArray([]); // true

Индексация массивов

Числовые индексы:

const arr = [1, 2, 3];
// Фактически создаётся объект:
// {
//   0: 1,
//   1: 2, 
//   2: 3,
//   length: 3,
//   __proto__: Array.prototype
// }

Строковые ключи:

const arr = [1, 2, 3];
arr["test"] = "значение"; // Строковой ключ
console.log(arr.test); // "значение"
console.log(arr.length); // 3 — не изменилось!

Ключевой момент: Когда строка может быть преобразована в число, она используется как числовой индекс:

arr["2"] = 100; // Эквивалентно arr[2] = 100
arr["07"] = 200; // "07" → строковой ключ, не числовой индекс

Свойство length

Особенности поведения:

  1. Автоматическое обновление:
const arr = [1, 2, 3];
arr[10] = 99;
console.log(arr.length); // 11
  1. Двусторонняя связь:
const arr = [1, 2, 3, 4, 5];
arr.length = 2;
console.log(arr); // [1, 2] - элементы удалены!

arr.length = 5;
console.log(arr); // [1, 2, empty × 3] - пустые слоты
  1. Формула: length = наибольший_числовой_индекс + 1
const arr = [];
arr[100] = "x";
console.log(arr.length); // 101

Пустые слоты (Holes) vs undefined

Различие:

// 1. Массив с undefined значениями
const arr1 = [undefined, undefined, undefined];
console.log(arr1); // [undefined, undefined, undefined]
console.log(0 in arr1); // true - свойство существует

// 2. Массив с пустыми слотами
const arr2 = [];
arr2.length = 3;
console.log(arr2); // [empty × 3]
console.log(0 in arr2); // false - свойства нет!

Создать пустые слоты можно несколькими способами:

  1. Увеличение length
  2. Пропуск индексов при создании
  3. Удаление элементов с delete

Поведение методов массивов

Методы, которые ПРОПУСКАЮТ пустые слоты (не вызывают callback для них):

const sparse = [1, , 3, , 5]; // Индексы 0, 2, 4 существуют

// forEach() - пропускает пустые слоты
let count = 0;
sparse.forEach((v, i) => {
  console.log(`Индекс ${i}: ${v}`);
  count++;
});
// Вывод: Индекс 0: 1, Индекс 2: 3, Индекс 4: 5
// count = 3 (а не 5!)

// map() - тоже пропускает пустые слоты!
const mapped = sparse.map((v, i) => {
  console.log(`map вызван для индекса ${i}`);
  return v * 2;
});
// Вывод: map вызван для индекса 0, map вызван для индекса 2, map вызван для индекса 4
console.log(mapped); // [2, empty, 6, empty, 10]

// filter() - тоже пропускает
const filtered = sparse.filter(v => v > 2);
console.log(filtered); // [3, 5] - без пустых слотов!

// reduce() - начинается с первого существующего элемента
const sum = sparse.reduce((acc, v) => acc + v, 0);
console.log(sum); // 9 (1 + 3 + 5), а не NaN или ошибка

Методы, которые НЕ ПРОПУСКАЮТ пустые слоты (обрабатывают их как undefined):

const sparse = [1, , 3];

// Array.from() БЕЗ функции-маппера преобразует empty в undefined
const fromSparse = Array.from(sparse);
console.log(fromSparse); // [1, undefined, 3]
console.log(1 in fromSparse); // true - теперь свойство существует!

// Array.from() С функцией-маппером сначала преобразует, потом применяет
const mappedFrom = Array.from(sparse, x => (x || 0) * 2);
console.log(mappedFrom); // [2, 0, 6] - empty стал 0 после преобразования

// Spread оператор тоже преобразует
const spread = [...sparse];
console.log(spread); // [1, undefined, 3]

Особые случаи:

const sparse = [1, , 3];

// join() - преобразует пустые слоты в пустые строки
console.log(sparse.join('-')); // "1--3"

// Object.keys/values/entries() - видят только существующие свойства
console.log(Object.keys(sparse)); // ["0", "2"]
console.log(Object.values(sparse)); // [1, 3]

// for...in - аналогично Object.keys()
for (let key in sparse) {
  console.log(key); // "0", "2"
}

// for...of - ведёт себя как values() для массива
for (let value of sparse) {
  console.log(value); // 1, 3 (пустой слот пропущен!)
}

Итого

  1. Массивы — это объекты: можно использовать строковые ключи
  2. length автоматический: зависит от максимального числового индекса
  3. Пустые ≠ undefined: empty-слоты пропускаются методами массивов
  4. Приведение типов: a["7"]a[7]