Skip to main content

Щільні та розріджені масиви

Щільний (dense) масив, це масив, у якому зайняті всі індекси від 0 до length - 1, а розріджений (sparse), це масив, у якому деякі індекси відсутні, тобто в ньому є «дірки». Різниця не косметична: від неї залежить, які елементи побачать методи перебору і наскільки швидко двигун працюватиме з масивом.

Теорія

TL;DR

  • Щільний масив: усі індекси від 0 до length - 1 реально існують.
  • Розріджений масив: частина індексів відсутня, length більший за кількість справжніх елементів.
  • Дірка, це не значення undefined: властивості з таким ключем просто немає, тому i in arr дає false.
  • Дірки з'являються трьома шляхами: delete, пропуск у літералі [1, , 3], ручне збільшення length.
  • forEach, map, filter, reduce та Object.keys дірки пропускають, а for, for...of, spread і Array.from бачать їх як undefined.
  • Розріджені масиви повільніші, тому замість дірок краще зберігати явні null або undefined.

Швидкий приклад

javascript
const dense = [1, 2, 3]; const sparse = [1, , 3]; dense.forEach(v => console.log(v)); // 1, 2, 3 sparse.forEach(v => console.log(v)); // 1, 3 (індекс 1 пропущено) console.log(Object.keys(dense)); // ['0', '1', '2'] console.log(Object.keys(sparse)); // ['0', '2']

Обидва масиви мають length === 3, але справжніх елементів у другому лише два.

Визначення

Щільний масив (dense array), це масив, у якому всі індекси зайняті елементами, від 0 до length - 1.

Тобто:

  • немає «пропущених» індексів (дірок),
  • кожен індекс справді існує в об'єкті.
javascript
const dense = [10, 20, 30];

Тут:

  • індекси: 0, 1, 2
  • dense.length === 3
  • 0 in dense дає true, 1 in dense дає true, 2 in dense дає true

Розріджений масив (sparse array), це масив, у якому деякі індекси відсутні, у ньому є «дірки».

javascript
const sparse = [10, , 30]; console.log(sparse); // [10, <1 empty item>, 30]

Тут:

  • індекс 1 не існує (1 in sparse дає false)
  • sparse.length === 3, але елементів реально лише два

Як з'являються «дірки»

  1. Через використання delete:

    javascript
    const arr = [1, 2, 3]; delete arr[1]; console.log(arr); // [1, <1 empty item>, 3]
  2. Через пропуск у літералі:

    javascript
    const arr = [1, , 3];
  3. Через ручне збільшення length:

    javascript
    const arr = [1, 2]; arr.length = 5; console.log(arr); // [1, 2, <3 empty items>]
  4. Через запис у далекий індекс:

    javascript
    const arr = [1]; arr[4] = 5; console.log(arr); // [1, <3 empty items>, 5]

Окремо варто знати: new Array(3) теж створює розріджений масив з трьох дірок, а Array.from({ length: 3 }) дає щільний масив із трьох undefined.

Поведінка під час перебору

МетодЩільний масивРозріджений масив
forпроходить по всіх індексахпроходить по всіх індексах, дірка дає undefined
for...ofпроходить по всіх значенняхпроходить по всіх позиціях, дірка дає undefined
forEach()проходить по кожному елементупропускає «порожні»
map()обробляє кожен елементпропускає «порожні», але зберігає дірки в результаті
filter(), reduce(), some(), every()обробляють кожен елементпропускають «порожні»
inперевіряє наявність індексуповертає false для дірок
Object.keys()показує всі індексипропускає порожні
spread [...arr], Array.from()копіює значенняперетворює дірки на undefined
join(), toString()звичайний результатдірка стає порожнім рядком

Головна пастка тут у тому, що одна й та сама дірка в одних методах «не існує», а в інших перетворюється на undefined.

javascript
const sparse = [1, , 3]; console.log(sparse.map(v => v * 2)); // [2, <1 empty item>, 6] console.log([...sparse]); // [1, undefined, 3] console.log(sparse.join('-')); // '1--3'

Вплив на продуктивність

Розріджені масиви:

  • працюють повільніше,
  • гірше оптимізуються двигуном V8, бо він переводить масив з швидкого «елементного» представлення у словникове,
  • і часто поводяться «дивно» під час ітерації.

Тому в реальних проєктах краще уникати «дірявих» масивів і використовувати null або undefined як явні значення «порожнечі».

Якщо розріджений масив уже утворився, його легко ущільнити:

javascript
const sparse = [1, , 3]; const filled = Array.from(sparse); // [1, undefined, 3] const cleaned = sparse.filter(() => true); // [1, 3], дірки прибрано

Коротко

Тип масивуОписПрикладПоведінка
ЩільнийУсі індекси зайняті[1, 2, 3]Передбачувана, швидка
РозрідженийЄ пропущені індекси[1, , 3]Методи пропускають порожні, повільніше

Типові помилки

  • Вважати, що дірка, це undefined. Значення немає взагалі; перевіряйте через i in arr або Object.hasOwn(arr, i), а не через порівняння з undefined.
  • Довіряти length як кількості елементів. У розрідженому масиві length більший за реальну кількість елементів; рахуйте через Object.keys(arr).length.
  • Створювати масив через new Array(n) і перебирати його. new Array(3).map(...) нічого не зробить, бо всі позиції, це дірки; беріть Array.from({ length: 3 }, (_, i) => i).
  • Очікувати однакової поведінки від усіх методів. map() дірки пропускає, а spread і for...of перетворюють їх на undefined.
  • Використовувати delete для видалення елементів. Саме так у продакшн-коді найчастіше й з'являються розріджені масиви; беріть splice().
  • Збільшувати length вручну, щоб «зарезервувати місце». Це не прискорює код, а навпаки, робить масив розрідженим.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.