Синтаксис розгортання (...)
Синтаксис розгортання (...
) дає ітерованим елементам (таким, як вираз із масивом чи рядок) змогу розгортатися в тих місцях, де очікується нуль чи більше аргументів (у викликах функцій) чи елементів (для літералів масиву). Для літерала об'єкта синтаксис розгортання перелічує властивості об'єкта й додає пари ключ-значення до об'єкта, що створюється.
Синтаксис розгортання має такий самий вигляд, як синтаксис решти. У певному розумінні синтаксис розгортання є протилежністю синтаксису решти. Синтаксис розгортання "розпускає" масив на його елементи, натомість синтаксис решти збирає декілька елементів і "стискає" їх в один елемент. Дивіться решту параметрів і решту властивостей.
Спробуйте його в дії
Синтаксис
myFunction(a, ...iterableObj, b)
[1, ...iterableObj, '4', "п'ять", 6]
{ ...obj, key: 'значення' }
Опис
Синтаксис розгортання може використовуватися тоді, коли всі елементи об'єкта чи масиву треба включити в новий масив чи об'єкт, або коли їх треба один за одним застосувати в списку аргументів при виклику функції. Є три різні місця, що приймають синтаксис розгортання:
- Список аргументів функції (
myFunction(a, ...iterableObj, b)
) - Літерали масивів (
[1, ...iterableObj, '4', "п'ять", 6]
) - Літерали об'єктів (
{ ...obj, key: 'value' }
)
Хоч синтаксис має однаковий вигляд, ці місця мають дещо різну семантику.
Лише ітеровані значення, як то Array
та String
, можуть бути розгорнуті в літералах масивів та списках аргументів. Чимало об'єктів не є ітерованими, включно з усіма простими об'єктами, що не мають метода Symbol.iterator
:
const obj = { key1: "value1" };
const array = [...obj]; // TypeError: obj is not iterable
З іншого боку, розгортання в літералах об'єктів перелічує власні властивості значення. В типових масивів усі індекси є перелічуваними власними властивостями, тому масиви можуть бути розгорнуті в об'єкти.
const array = [1, 2, 3];
const obj = { ...array }; // { 0: 1, 1: 2, 2: 3 }
Усі примітиви можуть бути розгорнуті в об'єктах. Лише рядки мають перелічувані власні властивості, а розгортання будь-чого іншого не додасть новому об'єктові жодних властивостей.
const obj = { ...true, ..."test", ...10 };
// { '0': 't', '1': 'e', '2': 's', '3': 't' }
При використанні синтаксису розгортання для викликів функцій слід мати на увазі можливість перевищення обмеження кількості аргументів рушія JavaScript. Дивіться Function.prototype.apply()
для отримання подробиць.
Приклади
Розгортання у викликах функцій
Заміна apply()
Зазвичай у тих випадках, де потрібно передати елементи масиву як аргументи до функції, використовують Function.prototype.apply()
.
function myFunction(x, y, z) {}
const args = [0, 1, 2];
myFunction.apply(null, args);
За допомогою синтаксису розгортання наведений вище приклад можна записати так:
function myFunction(x, y, z) {}
const args = [0, 1, 2];
myFunction(...args);
Будь-який аргумент зі списку може використовувати синтаксис розгортання. Також цей синтаксис можна вживати декілька разів.
function myFunction(v, w, x, y, z) {}
const args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);
Застосування до оператора new
Під час виклику конструктора з оператором new
не можна безпосередньо використати масив і apply()
, тому що apply()
викликає цільову функцію замість її конструювання, а отже, серед іншого, new.target
дорівнюватиме undefined
. Проте масив легко можна використовувати з new
, завдяки синтаксису розгортання:
const dateFields = [1970, 0, 1]; // 1 січня 1970
const d = new Date(...dateFields);
Розгортання в літералах масивів
Потужніший літерал масиву
Коли не використовувати синтаксис розгортання, то синтаксису літерала масиву вже не достатньо для створення нового масиву із використанням наявного масиву як однієї з його частин. Замість цього треба використовувати імперативний код із комбінацією методів, серед яких push()
, splice()
, concat()
тощо. За допомогою синтаксису розгортання це стає набагато коротше:
const parts = ["плечі", "коліна"];
const lyrics = ["голова", ...parts, "й", "пальці"];
// [ "голова", "плечі", "коліна", "й", "пальці" ]
Точнісінько як для розгортання списку аргументів, ...
можна вживати будь-де всередині масиву, і робити це більш ніж однократно.
Копіювання масиву
Синтаксис розгортання можна використовувати для створення поверхневої копії масиву. Кожний елемент масиву зберігає свою ідентичність, не бувши скопійованим.
const arr = [1, 2, 3];
const arr2 = [...arr]; // так само, як arr.slice()
arr2.push(4);
// arr2 стає [1, 2, 3, 4]
// arr залишається незміненим
Синтаксис розгортання проходить на один рівень вглиб масиву під час копіювання. У зв'язку з чим він може не підходити для копіювання багатовимірних масивів. Те саме стосується комбінації синтаксису розгортання із Object.assign()
: жодна нативна операція JavaScript не виконує глибокого клонування. Метод веб API structuredClone()
дає змогу виконувати глибоке копіювання значень певних підтримуваних типів. Дивіться детальніше на сторінці Поверхневе копіювання.
const a = [[1], [2], [3]];
const b = [...a];
b.shift().shift();
// 1
// ОЙ лишенько! Це також вплинуло і на масив 'a':
console.log(a);
// [[], [2], [3]]
Кращий спосіб з'єднання масивів
Array.prototype.concat()
часто вживається для приєднання масиву до кінця вже наявного масиву. Без застосування синтаксису розгортання це можна зробити так:
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
// Додати всі елементи масиву arr2 у масив arr1
arr1 = arr1.concat(arr2);
З появою синтаксису розгортання це перетворилося на:
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2];
// arr1 тепер дорівнює [0, 1, 2, 3, 4, 5]
Array.prototype.unshift()
часто вживається для приєднання масиву до початку вже наявного масиву. Без застосування синтаксису розгортання це можна зробити так:
const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
// Приєднати всі елементи масиву arr2 на початку arr1
Array.prototype.unshift.apply(arr1, arr2);
console.log(arr1); // [3, 4, 5, 0, 1, 2]
З появою синтаксису розгортання це перетворилося на:
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1];
console.log(arr1); // [3, 4, 5, 0, 1, 2]
[!NOTE] На відміну від
unshift()
, цей код створює новийarr1
, а не модифікує початковий масивarr1
на місці.
Умовне додавання значень до масиву
Елемент можна зробити присутнім або відсутнім у літералі масиву, залежно від певної умови, за допомогою умовного оператора.
const isSummer = false;
const fruits = ["яблуко", "банан", ...(isSummer ? ["кавун"] : [])];
// ['яблуко', 'банан']
Коли умова – false
, то розгортається порожній масив, тож нічого не додається в кінцевий масив. Зауважте, що це відрізняється від наступного:
const fruits = ["яблуко", "банан", isSummer ? "кавун" : undefined];
// ['яблуко', 'банан', undefined]
В цьому випадку додається додатковий елемент undefined
, коли isSummer
дорівнює false
, і цей елемент буде оброблятися методами штибу Array.prototype.map()
.
Розгортання в об'єктних літералах
Копіювання та злиття об'єктів
Синтаксис розгортання можна використовувати для злиття кількох об'єктів у один.
const obj1 = { foo: "bar", x: 42 };
const obj2 = { bar: "baz", y: 13 };
const mergedObj = { ...obj1, ...obj2 };
// { foo: "bar", x: 42, bar: "baz", y: 13 }
Одне розгортання утворює поверхневу копію вихідного об'єкта (але без його неперелічуваних властивостей і без копіювання прототипу), подібно до копіювання масиву.
const clonedObj = { ...obj1 };
// { foo: "bar", x: 42 }
Перевизначення властивостей
Коли один об'єкт розгортається в інший, або коли кілька об'єктів розгортаються в один, і зустрічаються властивості з однаковими назвами, то така властивість бере останнє значення з присвоєних, залишаючись на позиції, на якій спершу була визначена.
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const mergedObj = { x: 41, ...obj1, ...obj2, y: 9 }; // { x: 42, foo: "baz", y: 9 }
Умовне додавання властивостей до об'єкта
Елемент можна зробити присутнім або відсутнім в об'єктному літералі залежно від умови, використавши умовний оператор.
const isSummer = false;
const fruits = {
apple: 10,
banana: 5,
...(isSummer ? { watermelon: 30 } : {}),
};
// { apple: 10, banana: 5 }
Варіант на випадок того, що умова дорівнює false
, – порожній об'єкт, тож тоді нічого не розгортається в кінцевий об'єкт. Зауважте, що це відрізняється від наступного:
const fruits = {
apple: 10,
banana: 5,
watermelon: isSummer ? 30 : undefined,
};
// { apple: 10, banana: 5, watermelon: undefined }
У такому випадку властивість watermelon
завжди присутня, і її будуть обробляти методи штибу Object.keys()
.
Оскільки примітиви також можна розгортати в об'єкти, і враховуючи те, що всі хибні значення не мають перелічуваних властивостей, можна просто використати логічний оператор І:
const isSummer = false;
const fruits = {
apple: 10,
banana: 5,
...(isSummer && { watermelon: 30 }),
};
У такому випадку, якщо isSummer
є хибним значенням, то на об'єкті fruits
не створюється жодної властивості.
Порівняння з Object.assign()
Зауважте, що Object.assign()
може застосовуватись для модифікації об'єкта, а от синтаксис розгортання — ні.
const obj1 = { foo: "bar", x: 42 };
Object.assign(obj1, { x: 1337 });
console.log(obj1); // { foo: "bar", x: 1337 }
На додачу, Object.assign()
провокує виклик сетерів цільового об'єкта, а синтаксис розгортання – ні.
const objectAssign = Object.assign(
{
set foo(val) {
console.log(val);
},
},
{ foo: 1 },
);
// Друкує "1"; objectAssign.foo досі містить первинний сетер
const spread = {
set foo(val) {
console.log(val);
},
...{ foo: 1 },
};
// Нічого не друкується; spread.foo дорівнює 1
Неможливо в нативному коді повторно реалізувати функцію Object.assign()
за допомогою одного розгортання:
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const merge = (...objects) => ({ ...objects });
const mergedObj1 = merge(obj1, obj2);
// { 0: { foo: 'bar', x: 42 }, 1: { foo: 'baz', y: 13 } }
const mergedObj2 = merge({}, obj1, obj2);
// { 0: {}, 1: { foo: 'bar', x: 42 }, 2: { foo: 'baz', y: 13 } }
У наведеному вище прикладі синтаксис розгортання працює не так, як можна було очікувати: він розгортає масив аргументів у об'єктний літерал, через параметр решти. Нижче – інша реалізація функції merge
із застосуванням синтаксису розгортання, поведінка якої аналогічна до Object.assign()
, за винятком того, що вона не змушує спрацьовувати сетери й не модифікує жодного об'єкта:
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const merge = (...objects) =>
objects.reduce((acc, cur) => ({ ...acc, ...cur }));
const mergedObj1 = merge(obj1, obj2);
// { foo: 'baz', x: 42, y: 13 }
Специфікації
Сумісність із браузерами
desktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Spread in array literals | Chrome Full support 46 | Edge Full support 12 | Firefox Full support 16 | Internet Explorer No support Ні | Opera Full support 37 | Safari Full support 8 | WebView Android Full support 46 | Chrome Android Full support 46 | Firefox for Android Full support 16 | Opera Android Full support 37 | Safari on iOS Full support 8 | Samsung Internet Full support 5.0 | Deno Full support 1.0 | Node.js Full support 5.0.0 |
Spread in function calls | Chrome Full support 46 | Edge Full support 12 | Firefox Full support 27 | Internet Explorer No support Ні | Opera Full support 37 | Safari Full support 8 | WebView Android Full support 46 | Chrome Android Full support 46 | Firefox for Android Full support 27 | Opera Android Full support 37 | Safari on iOS Full support 8 | Samsung Internet Full support 5.0 | Deno Full support ?null | Node.js Full support 5.0.0 |
Spread in object literals | Chrome Full support 60 | Edge Full support 79 | Firefox Full support 55 | Internet Explorer No support Ні | Opera Full support 47 | Safari Full support 11.1 | WebView Android Full support 60 | Chrome Android Full support 60 | Firefox for Android Full support 55 | Opera Android Full support 44 | Safari on iOS Full support 11.3 | Samsung Internet Full support 8.2 | Deno Full support ?null | Node.js Full support 8.3.0 |