Синтаксис розгортання (...)
Синтаксис розгортання (...
) дає ітерованим елементам (таким, як вираз із масивом чи рядок) змогу розгортатися в тих місцях, де очікується нуль чи більше аргументів (у викликах функцій) чи елементів (для літералів масиву). Для літерала об'єкта синтаксис розгортання перелічує властивості об'єкта й додає пари ключ-значення до об'єкта, що створюється.
Синтаксис розгортання має такий самий вигляд, як синтаксис решти. У певному розумінні синтаксис розгортання є протилежністю синтаксису решти. Синтаксис розгортання "розпускає" масив на його елементи, натомість синтаксис решти збирає декілька елементів і "стискає" їх в один елемент. Дивіться решту параметрів і решту властивостей.
Спробуйте його в дії
Синтаксис
myFunction(a, ...iterableObj, b)
[1, ...iterableObj, '4', "п'ять", 6]
{ ...obj, key: 'значення' }
Опис
Синтаксис розгортання може використовуватися тоді, коли всі елементи об'єкта чи масиву треба включити в новий масив чи об'єкт, або коли їх треба один за одним застосувати в списку аргументів при виклику функції. Є три різні місця, що приймають синтаксис розгортання:
- Список аргументів функції (
myFunction(a, ...iterableObj, b)
) - Літерали масивів (
[1, ...iterableObj, '4', "п'ять", 6]
) - Літерали об'єктів (
{ ...obj, key: 'value' }
)
Хоч синтаксис має однаковий вигляд, ці місця мають дещо різну семантику.
Лише ітеровані об'єкти, як то Array
, можуть бути розгорнуті в масив та параметри функції. Чимало об'єктів не є ітерованими, включно з усіма простими об'єктами, що не мають метода 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 }
При використанні синтаксису розгортання для викликів функцій слід мати на увазі можливість перевищення обмеження кількості аргументів рушія 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 не виконує глибокого клонування. Метод веб APIstructuredClone()
дає змогу виконувати глибоке копіювання значень певних підтримуваних типів.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]
Примітка: На відміну від
unshift()
, цей код створює новийarr1
, а не модифікує початковий масивarr1
на місці.
Розгортання в об'єктних літералах
Поверхневе клонування (без врахування прототипа) чи злиття об'єктів є можливим із застосуванням стислішого синтаксису, ніж Object.assign()
.
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const clonedObj = { ...obj1 };
// { foo: "bar", x: 42 }
const mergedObj = { ...obj1, ...obj2 };
// { foo: "baz", x: 42, y: 13 }
Зауважте, що 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 No | 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 No | 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 No | 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 |