Перш ніж ми дізнаємось, як саме покращити продуктивність spread-оператора у JavaScript, заглянемо під капот роботи з ітерованими об'єктами самого оператора.
Оператор spread, або три крапки, працює з масивами чи загалом з ітерованими об'єктами та розділяє їх на елементи, з яких формується новий масив.
Spread-оператор може стояти на будь-якій позиції всередині літералу масиву:
const numbers = [1, 2, 3];
[0, ...numbers]; // => [0, 1, 2, 3]
[0, ...numbers, 4]; // => [0, 1, 2, 3, 4]
[...numbers, 4]; // => [1, 2, 3, 4]
А тепер цікаве запитання: чи залежить продуктивність коду від розташування spread-оператора в масиві? Дізнаємось!
1. Створюємо допоміжні функції
Перед тим як почнемо порівняння продуктивності, оголосимо дві функції. Перша з них — appendToTail()
:
function appendToTail(item, array) {
return [...array, item];
}
const numbers = [1, 2, 3];
appendToTail(10, numbers); // => [1, 2, 3, 10]
Суть створеної функції в тому, що вона вставляє числа в кінець масиву. Для цього ми використовуємо конструкцію [...array, item]
.
Наступна функція appendToHead()
:
function appendToHead(item, array) {
return [item, ...array];
}
const numbers = [1, 2, 3];
appendToHead(10, numbers); // => [10, 1, 2, 3]
appendToHead()
— чиста функція, яка повертає новий масив, де елементи додаються в початок масиву. Для цього використовуємо конструкцію [item, ...array]
.
Вам може здатися, що не має жодної різниці в продуктивності цих функцій. Але...
2. Тест продуктивності
Протестуємо швидкість конструкцій [...array, item]
vs [item, ...array]
на MacBook Pro в таких браузерах:
- Chrome 76
- Firefox 68
- Safari 12.1
Результати перевірки:

Бачимо, що в браузерах Firefox та Safari обидва варіанти мають приблизно однакову продуктивність.
А от в Chrome конструкція [...array, item]
відпрацьовує вдвічі швидше, ніж [item, ...array]
.
Тож щоб пришвидшити продуктивність spread-оператора у Chrome, використовуйте його на початку літералу масиву:
const result = [...array, item];
Виникає запитання: чому так відбувається?
3. Fast-path оптимізація
Починаючи з версії 7.2
, рушій V8, який відповідає за виконання JavaScript у Chrome, підтримує оптимізацію spread-оператора: fast-path оптимізацію.
Коротко пояснимо принцип її роботи.
Без оптимізації, коли рушій натрапляє на оператор spread, викликається метод ітератора iterator.next()
для ітерованого об'єкта. На кожній ітерації пам'ять кінцевого масиву збільшується і додається новий елемент.
Однак з fast-path оптимізацією ми уникаємо створення об'єкта ітератора взагалі. Рушій визначає довжину розширеного масиву і виділяє пам'ять лише раз для кінцевого масиву. Далі кожен елемент розширеного масиву додається в кінцевий. У такий спосіб і збільшується продуктивність.
4. Підтримувані структури даних
Fast-path оптимізація застосовується до таких структур даних у JavaScript:
Масиви
const numbers = [1, 2, 3, 4];
[...numbers, 5]; // => [1, 2, 3, 4, 5]
Рядки
const message = 'Hi';
[...message, '!']; // => ['H', 'i', '!']
Сети
const colors = new Set(['blue', 'white']);
[...colors, 'green']; // => ['blue', 'white', 'green']
[...colors.values(), 'green']; // => ['blue', 'white', 'green']
[...colors.keys(), 'green']; // => ['blue', 'white', 'green']
Map
Підтримується лише для методів map.keys()
та map.values()
:
const names = new Map([[5, 'five'], [7, 'seven']]);
[...names.values(), 'ten']; // => ['five', 'seven', 'ten']
[...names.keys(), 10]; // => [5, 7, 10]
Висновок
Коли ми використовуємо оператор spread на початку масиву, продуктивність коду збільшується завдяки fast-path оптимізації. Вона доступна в рушії V8 з версії 7.2 (представлено у Chrome v72
та NodeJS v12
).
Тести продуктивності показують, що можна досягнути подвійного пришвидшення, якщо використовувати конструкцію [...array, item]
, а не [item, ...array]
.
Звісно, fast-path — корисна річ, однак варто звертати на це увагу лише при роботі з великими масивами, коли продуктивність дійсно дуже важлива.
Ще немає коментарів