Как устроены массивы в JavaScript под капотом: packed, holey и elements kinds в V8?
Массивы и списки
JS-массив — это объект, но V8 хранит его индексированные элементы отдельно в плотном буфере и помечает «видом элементов» (elements kind): от него зависит, какие оптимизации применит JIT.
Основные виды
PACKED_SMI_ELEMENTS— только малые целые (Smi), самый быстрый вариант.PACKED_DOUBLE_ELEMENTS— числа с плавающей точкой, хранятся «распакованными» doubles.PACKED_ELEMENTS— произвольные значения: строки, объекты, смесь типов.HOLEY_*— те же виды, но с дырами: при чтении дыры движок обязан проверить цепочку прототипов.DICTIONARY_ELEMENTS— разреженный массив в виде хеш-таблицы, например послеarr[1e7] = 1на пустом массиве.
Переходы только в одну сторону
[1, 2]→push(1.5)→ DOUBLE →push('a')→ ELEMENTS.- Удаление строки или дыры не вернёт массив в SMI или PACKED: вид «портится» навсегда.
new Array(3)сразу создаёт HOLEY-массив, даже если потом заполнить все ячейки.
Практические выводы
- Не создавайте дыры и не читайте за границей массива (
arr[arr.length]): это уводит в медленный путь. - Держите массив однотипным: числа отдельно от объектов.
-0,NaNиInfinityне являются Smi — один такой элемент переводит массив в DOUBLE.
Коротко для собеседования
V8 оптимизирует массивы по виду элементов, и вид может только ухудшаться: смешали типы или создали дыру — массив навсегда уходит в более медленный режим. На практике заполняйте массивы подряд, держите их однотипными и не читайте за границей; проверить вид можно через %DebugPrint с флагом --allow-natives-syntax.