Щільні та розріджені масиви
Щільний (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.
Швидкий приклад
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.
Тобто:
- немає «пропущених» індексів (дірок),
- кожен індекс справді існує в об'єкті.
const dense = [10, 20, 30];Тут:
- індекси:
0, 1, 2 dense.length === 30 in denseдаєtrue,1 in denseдаєtrue,2 in denseдаєtrue
Розріджений масив (sparse array), це масив, у якому деякі індекси відсутні, у ньому є «дірки».
const sparse = [10, , 30];
console.log(sparse); // [10, <1 empty item>, 30]Тут:
- індекс
1не існує (1 in sparseдаєfalse) sparse.length === 3, але елементів реально лише два
Як з'являються «дірки»
-
Через використання
delete:javascriptconst arr = [1, 2, 3]; delete arr[1]; console.log(arr); // [1, <1 empty item>, 3] -
Через пропуск у літералі:
javascriptconst arr = [1, , 3]; -
Через ручне збільшення
length:javascriptconst arr = [1, 2]; arr.length = 5; console.log(arr); // [1, 2, <3 empty items>] -
Через запис у далекий індекс:
javascriptconst 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.
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як явні значення «порожнечі».
Якщо розріджений масив уже утворився, його легко ущільнити:
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вручну, щоб «зарезервувати місце». Це не прискорює код, а навпаки, робить масив розрідженим.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.