Синтаксис розгортання (...)

Синтаксис розгортання (...) дає ітерованим елементам (таким, як вираз із масивом чи рядок) змогу розгортатися в тих місцях, де очікується нуль чи більше аргументів (у викликах функцій) чи елементів (для літералів масиву). Для літерала об'єкта синтаксис розгортання перелічує властивості об'єкта й додає пари ключ-значення до об'єкта, що створюється.

Синтаксис розгортання має такий самий вигляд, як синтаксис решти. У певному розумінні синтаксис розгортання є протилежністю синтаксису решти. Синтаксис розгортання "розпускає" масив на його елементи, натомість синтаксис решти збирає декілька елементів і "стискає" їх в один елемент. Дивіться решту параметрів і решту властивостей.

Спробуйте його в дії

Синтаксис

myFunction(a, ...iterableObj, b)
[1, ...iterableObj, '4', "п'ять", 6]
{ ...obj, key: 'значення' }

Опис

Синтаксис розгортання може використовуватися тоді, коли всі елементи об'єкта чи масиву треба включити в новий масив чи об'єкт, або коли їх треба один за одним застосувати в списку аргументів при виклику функції. Є три різні місця, що приймають синтаксис розгортання:

Хоч синтаксис має однаковий вигляд, ці місця мають дещо різну семантику.

Лише ітеровані об'єкти, як то 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 не виконує глибокого клонування. Метод веб 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]

Примітка: На відміну від 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
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
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

Дивіться також