Присвоєння з деструктуруванням

Синтаксис присвоєння з деструктуруванням — це вираз в JavaScript, який дає змогу розпакувати значення з масивів, або властивості з об'єктів, в окремі змінні.

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

Синтаксис

const [a, b] = array;
const [a, , b] = array;
const [a = aDefault, b] = array;
const [a, b, ...rest] = array;
const [a, , b, ...rest] = array;
const [a, b, ...{ pop, push }] = array;
const [a, b, ...[c, d]] = array;

const { a, b } = obj;
const { a: a1, b: b1 } = obj;
const { a: a1 = aDefault, b = bDefault } = obj;
const { a, b, ...rest } = obj;
const { a: a1, b: b1, ...rest } = obj;
const { [key]: a } = obj;

let a, b, a1, b1, c, d, rest, pop, push;
[a, b] = array;
[a, , b] = array;
[a = aDefault, b] = array;
[a, b, ...rest] = array;
[a, , b, ...rest] = array;
[a, b, ...{ pop, push }] = array;
[a, b, ...[c, d]] = array;

({ a, b } = obj); // дужки обов'язкові
({ a: a1, b: b1 } = obj);
({ a: a1 = aDefault, b = bDefault } = obj);
({ a, b, ...rest } = obj);
({ a: a1, b: b1, ...rest } = obj);

Опис

Літерали об'єктів та масивів дають можливість легко створювати пакунки з даними на ходу.

const x = [1, 2, 3, 4, 5];

Присвоєння з деструктуруванням використовує подібний синтаксис, але там він по лівий бік від оператора присвоєння. Цей синтаксис задає те, які саме значення зі змінної-джерела мають бути розпаковані.

const x = [1, 2, 3, 4, 5];
const [y, z] = x;
console.log(y); // 1
console.log(z); // 2

Подібним чином можна деструктурувати об'єкти з лівого боку від присвоєння.

const obj = { a: 1, b: 2 };
const { a, b } = obj;
// еквівалентно такому коду:
// const a = obj.a;
// const b = obj.b;

Ці можливості подібні до функціональності, що доступна в Perl і Python.

Можливості, що стосуються окремих випадків деструктурування масиву або об'єкта, дивіться в окремих прикладах нижче.

Зв'язування та присвоєння

Для деструктурування, як об'єктів, так і масивів, є два типи патернів деструктурування: патерн зв'язування й патерн присвоєння, що мають дещо відмінні синтаксиси.

В патернах зв'язування патерн починається з ключового слова оголошення (var, let чи const). Потім кожна окрема властивість мусить бути або зв'язана зі змінною, або деструктурована далі.

const obj = { a: 1, b: { c: 2 } };
const {
  a,
  b: { c: d },
} = obj;
// Зв'язані дві змінні: `a` та `d`

Всі змінні поділяють одне оголошення, тож якщо потрібно, аби частині змінній можна було повторно присвоїти значення, а частині – бути доступною лише для читання, треба деструктурувати двічі: один раз із let, один раз із const.

const obj = { a: 1, b: { c: 2 } };
const { a } = obj; // a є сталою
let {
  b: { c: d },
} = obj; // d можна повторно присвоїти значення

В багатьох інших записах, де мова зв'язує за вас змінну, можна використати патерн деструктурування. Серед таких записів:

В патернах присвоєння патерн не починається з ключового слова. Кожна деструктурована властивість присвоюється цілі присвоєння, котра може бути або оголошена заздалегідь з var чи let, або бути властивістю іншого об'єкта – загалом, може бути будь-чим, що може з'явитися зліва у виразі присвоєння.

const numbers = [];
const obj = { a: 1, b: 2 };
({ a: numbers[0], b: numbers[1] } = obj);
// Властивості `a` й `b` присвоюються властивостям `numbers`

Примітка: Дужки ( ... ) навколо інструкції присвоєння є обов'язковими, коли деструктурування присвоєння літерала об'єкта використовується без оголошення.

{ a, b } = { a: 1, b: 2 } не є дійсним синтаксисом сам по собі, оскільки {a, b} зліва вважається блоком коду, а не літералом об'єкта, згідно з правилами інструкцій-виразів. Проте ({ a, b } = { a: 1, b: 2 }) є дійсним, як і const { a, b } = { a: 1, b: 2 }.

Якщо використовується стиль коду без крапок з комою в кінці рядків, то вираз ( ... ) потребує крапки з комою перед ним, інакше він може бути використаний для виклику функції на попередньому рядку.

Зверніть увагу, що еквівалентний до коду вище патерн зв'язування не є дійсним синтаксисом:

const numbers = [];
const obj = { a: 1, b: 2 };
const { a: numbers[0], b: numbers[1] } = obj;
// Це еквівалентно щодо:
//   const numbers[0] = obj.a;
//   const numbers[1] = obj.b;
// Що абсолютно не є дійсним.

Патерни присвоєння можна використовувати лише зліва оператора присвоєння. Їх не можна використовувати вкупі зі складеними операторами присвоєння – +=, *=.

Усталене значення

Кожна деструктурована властивість може мати усталене значення. Усталене значення використовується, коли властивості немає, або коли її значення – undefined. Воно не використовується, коли її значення – null.

const [a = 1] = []; // a – 1
const { b = 2 } = { b: undefined }; // b – 2
const { c = 2 } = { c: null }; // c – null

Усталене значення може бути будь-яким виразом. Цей вираз буде обчислений лише коли треба.

const { b = console.log("hey") } = { b: 2 };
// Нічого не виводить, бо `b` – визначена, і немає потреби
// обчислювати усталене значення.

Решта властивостей

В кінці деструктурування можна поставити решту властивостей ...rest. Цей патерн збереже всю решту властивостей об'єкта чи масиву в новий об'єкт або масив.

const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(others); // { b: 2, c: 3 }

const [first, ...others2] = [1, 2, 3];
console.log(others2); // [2, 3]

Решта властивостей мусить бути останньою в патерні й не мати коми в кінці.

const [a, ...b] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma
// Завжди розглядайте використання оператор решти в кінці

Приклади

Деструктурування масиву

Просте присвоєння змінних

const foo = ["один", "два", "три"];

const [red, yellow, green] = foo;
console.log(red); // "один"
console.log(yellow); // "два"
console.log(green); // "три"

Деструктурування з більшою кількістю елементів, ніж має джерело

Якщо під час деструктурування масиву довжиною N, вказаного в правій частині виразу присвоєння, кількість змінних в лівій частині більша за N, то лише першим N змінним буде присвоєно значення. Значення решти змінних залишиться невизначеним.

const foo = ["один", "два"];

const [red, yellow, green, blue] = foo;
console.log(red); // "один"
console.log(yellow); // "два"
console.log(green); // undefined
console.log(blue); //undefined

Обмін змінних місцями

Значення двох змінних можна поміняти місцями в одному виразі з деструктуруванням.

Без присвоєння з деструктуруванням така заміна вимагатиме тимчасової змінної (або, в деяких низькорівневих мовах, застосування трюку зі XOR-заміною).

let a = 1;
let b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

const arr = [1, 2, 3];
[arr[2], arr[1]] = [arr[1], arr[2]];
console.log(arr); // [1, 3, 2]

Розбирання масиву, поверненого функцією

Завжди було можливо повернути масив із функції. Деструктурування може зробити роботу з поверненими значеннями масивів лаконічнішою.

В цьому прикладі f() повертає значення [1, 2], яке можна одразу, одним рядком, розібрати за допомогою деструктурування.

function f() {
  return [1, 2];
}

const [a, b] = f();
console.log(a); // 1
console.log(b); // 2

Ігнорування деяких повернених значень

Можна ігнорувати ті з повернених значень, які не становлять інтересу:

function f() {
  return [1, 2, 3];
}

const [a, , b] = f();
console.log(a); // 1
console.log(b); // 3

const [c] = f();
console.log(c); // 1

Можна також ігнорувати всі повернені значення:

[, ,] = f();

Використання патерну зв'язування як решти властивостей

Іще одним патерном зв'язування об'єкта може бути решта властивостей при деструктуруванні масиву. Внутрішнє деструктурування деструктурує масив, створений після збору решти елементів, тому ви не можете в такий спосіб отримати доступ до будь-яких властивостей, які присутні у вихідному ітерованому об'єкті.

const [a, b, ...{ length }] = [1, 2, 3];
console.log(a, b, length); // 1 2 1
const [a, b, ...[c, d]] = [1, 2, 3, 4];
console.log(a, b, c, d); // 1 2 3 4

Такі патерни зв'язування можна навіть вкладати один в одного, поки кожна решта властивостей є останньою у своєму списку.

const [a, b, ...[c, d, ...[e, f]]] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c, d, e, f); // 1 2 3 4 5 6

З іншого боку, деструктурування об'єкта може мати лише ідентифікатор як властивість решти.

const { a, ...{ b } } = { a: 1, b: 2 };
// SyntaxError: `...` must be followed by an identifier in declaration contexts
let a, b;
({ a, ...{ b } } = { a: 1, b: 2 });
// SyntaxError: `...` must be followed by an assignable reference in assignment contexts

Розпакування значень зі збігів з регулярним виразом

Коли метод регулярного виразу exec() знаходить збіг, він повертає масив, що містить спершу цілий збіг з частиною рядка, а потім ті частини рядка, які збіглися з виділеними дужками групами регулярного виразу. Присвоєння з деструктуруванням дає змогу легко виділити частини цього масиву, проігнорувавши повний збіг, якщо в ньому немає потреби.

function parseProtocol(url) {
  const parsedURL = /^(\w+):\/\/([^/]+)\/(.*)$/.exec(url);
  if (!parsedURL) {
    return false;
  }
  console.log(parsedURL);
  // ["https://webdoky.org/uk/docs/Web/JavaScript",
  // "https", "webdoky.org", "uk/docs/Web/JavaScript"]

  const [, protocol, fullhost, fullpath] = parsedURL;
  return protocol;
}

console.log(parseProtocol("https://webdoky.org/uk/docs/Web/JavaScript"));
// "https"

Використання деструктурування масиву на будь-якому ітерованому об'єкті

Деструктурування масиву звертається до протоколу ітерування з правого боку присвоєння. Таким чином, будь-який ітерований об'єкт можна деструктурувати, а не лише масиви.

const [a, b] = new Map([
  [1, 2],
  [3, 4],
]);
console.log(a, b); // [1, 2] [3, 4]

Неітеровані об'єкти не можна деструктурувати як масиви.

const obj = { 0: "a", 1: "b", length: 2 };
const [a, b] = obj;
// TypeError: obj is not iterable

Ітеровані об'єкти ітеруються лише поки не закінчиться список деструктурування.

const obj = {
  *[Symbol.iterator]() {
    for (const v of [0, 1, 2, 3]) {
      console.log(v);
      yield v;
    }
  },
};
const [a, b] = obj; // Виводить лише 0 і 1

Прив'язка решти обчислюється негайно, створюючи новий масив, а не використовує старий ітерований об'єкт.

const obj = {
  *[Symbol.iterator]() {
    for (const v of [0, 1, 2, 3]) {
      console.log(v);
      yield v;
    }
  },
};
const [a, b, ...rest] = obj; // Виводить 0 1 2 3
console.log(rest); // [2, 3] (масив)

Деструктурування об'єктів

Звичайне присвоєння

const user = {
  id: 42,
  isVerified: true,
};

const { id, isVerified } = user;

console.log(id); // 42
console.log(isVerified); // true

Присвоєння змінним із новими іменами

Можна розпакувати властивість з об'єкта і присвоїти її змінній під іншим ім'ям, відмінним від назви властивості.

const o = { p: 42, q: true };
const { p: foo, q: bar } = o;

console.log(foo); // 42
console.log(bar); // true

Тут, наприклад, const { p: foo } = o бере з об'єкта o властивість p і присвоює її місцевій змінній foo.

Присвоєння змінним із новими іменами, та надання усталених значень

Властивість можна одночасно:

  • Розпакувати з об'єкта і призначити змінній із відмінним іменем.
  • Призначити їй усталене значення, на випадок, якщо розпаковане значення дорівнює undefined.
const { a: aa = 10, b: bb = 5 } = { a: 3 };

console.log(aa); // 3
console.log(bb); // 5

Розпаковування властивостей об'єкта, переданого параметром функції

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

Розгляньмо цей об'єкт, який містить інформацію про користувача.

const user = {
  id: 42,
  displayName: "скішка",
  fullName: {
    firstName: "Самійло",
    lastName: "Кішка",
  },
};

Далі показано, як розпакувати властивість переданого об'єкта у змінну із таким самим ім'ям. Значення параметра { id } вказує, що з об'єкта, переданого до функції, слід розпакувати властивість id у змінну зі таким самим ім'ям. Цю змінну далі можна буде використовувати всередині функції.

function userId({ id }) {
  return id;
}

console.log(userId(user)); // 42

Можна задати нове ім'я для розпакованої змінної. Нижче розпаковується властивість під назвою displayName, і перейменовується у dname для використання всередині тіла функції.

function userDisplayName({ displayName: dname }) {
  return dname;
}

console.log(userDisplayName(user)); // "скішка"

Також можна розпаковувати вкладені об'єкти. Наведений нижче приклад ілюструє розпаковування властивості fullname.firstName у змінну, названу name.

function whois({ displayName, fullName: { firstName: name } }) {
  return `${displayName} — це ${name}`;
}

console.log(whois(user)); // "скішка — це Самійло"

Встановлення усталеного значення для параметра функції

За допомогою = можна вказати усталене значення, яке потім буде використано як значення змінної в разі, якщо вказаної властивості в початковому об'єкті не існує.

Нижче показано функцію, де усталене значення змінної size дорівнює 'великий', усталені координати дорівнюють x: 0, y: 0, а усталений радіус radius — 25.

function drawChart({
  size = "великий",
  coords = { x: 0, y: 0 },
  radius = 25,
} = {}) {
  console.log(size, coords, radius);
  // виконати малювання якогось графіка
}

drawChart({
  coords: { x: 18, y: 30 },
  radius: 30,
});

В сигнатурі наведеної вище функції drawChart деструктурована ліва частина має усталене значення порожнього об'єкта = {}.

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

Докладніше про це у розділі Усталені параметри > Деструктурування параметрів із присвоєнням усталених значень.

Деструктурування вкладеного об'єкта і масиву

const metadata = {
  title: "Scratchpad",
  translations: [
    {
      locale: "de",
      localizationTags: [],
      lastEdit: "2014-04-14T08:43:37",
      url: "/de/docs/Tools/Scratchpad",
      title: "JavaScript-Umgebung",
    },
  ],
  url: "/uk/docs/Tools/Scratchpad",
};

const {
  title: englishTitle, // перейменування
  translations: [
    {
      title: localeTitle, // перейменування
    },
  ],
} = metadata;

console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"

For of — ітерування із деструктуруванням

const people = [
  {
    name: "Михайло Коваль",
    family: {
      mother: "Яна Коваль",
      father: "Гаврило Коваль",
      sister: "Соломія Коваль",
    },
    age: 35,
  },
  {
    name: "Фома Іванченко",
    family: {
      mother: "Раїса Іванченко",
      father: "Ярослав Іванченко",
      brother: "Валерій Іванченко",
    },
    age: 25,
  },
];

for (const {
  name: n,
  family: { father: f },
} of people) {
  console.log(`Ім'я: ${n}, Батько: ${f}`);
}

// "Ім'я: Михайло Коваль, Батько: Гаврило Коваль"
// "Ім'я: Фома Іванченко, Батько: Ярослав Іванченко"

Деструктурування, і обчислені імена властивостей

Деструктурування дає змогу використовувати обчислені імена властивостей, подібні до таких в об'єктних літералах.

const key = "z";
const { [key]: foo } = { z: "bar" };

console.log(foo); // "bar"

Недійсний ідентифікатор JavaScript як ім'я властивості

Деструктуризацію можна використовувати із такими іменами властивостей, які не є дійсними ідентифікаторами в JavaScript, шляхом вказання такої альтернативи, яка є дійсним ідентифікатором.

const foo = { "fizz-buzz": true };
const { "fizz-buzz": fizzBuzz } = foo;

console.log(fizzBuzz); // true

Деструктурування примітивних значень

Деструктурування об'єктів майже рівносильне щодо звертання до властивостей. Це означає, що якщо спробувати деструктурувати примітивне значення, то значення загорнеться у відповідний об'єкт-обгортку, і властивість буде отримана з цього об'єкта-обгортки.

const { a, toFixed } = 1;
console.log(a, toFixed); // undefined ƒ toFixed() { [native code] }

Так само як зі звертанням до властивостей, деструктурування null і undefined викидає TypeError.

const { a } = undefined; // TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined.
const { b } = null; // TypeError: Cannot destructure property 'b' of 'null' as it is null.

Це відбудеться навіть якщо патерн – порожній.

const {} = null; // TypeError: Cannot destructure 'null' as it is null.

Одночасне деструктурування об'єкта і масиву

Можна комбінувати деструктурування об'єкта і масиву. Скажімо, потрібен третій елемент з масиву props, наведеного нижче. І далі потрібно вибрати з нього властивість name. Це можна зробити наступним чином:

const props = [
  { id: 1, name: "Fizz" },
  { id: 2, name: "Buzz" },
  { id: 3, name: "FizzBuzz" },
];

const [, , { name }] = props;

console.log(name); // "FizzBuzz"

Під час деструктурування об'єкта відбувається звертання до прототипного ланцюжка

Якщо під час деструктурування об'єкта властивість в ньому недоступна, пошук продовжиться далі вздовж ланцюжка прототипів.

const obj = {
  self: "123",
  __proto__: {
    prot: "456",
  },
};
const { self, prot } = obj;

console.log(self); // "123"
console.log(prot); // "456"

Специфікації

Сумісність із браузерами

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
Destructuring assignment
Chrome Full support 49
Edge Full support 14
Firefox Full support 41
footnote
Internet Explorer No support No
Opera Full support 36
Safari Full support 8
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 41
footnote
Opera Android Full support 36
Safari on iOS Full support 8
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
Computed property names
Chrome Full support 49
Edge Full support 14
Firefox Full support 41
Internet Explorer No support No
Opera Full support 36
Safari Full support 10
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 41
Opera Android Full support 36
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
Rest in arrays
Chrome Full support 49
Edge Full support 16
Firefox Full support 41
Internet Explorer No support No
Opera Full support 36
Safari Full support 9.1
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 41
Opera Android Full support 36
Safari on iOS Full support 9.3
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
Rest in objects
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.0
Deno Full support 1.0
Node.js Full support 8.3.0

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