Присвоєння з деструктуруванням
Синтаксис присвоєння з деструктуруванням — це вираз в 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 можна повторно присвоїти значення
В багатьох інших записах, де мова зв'язує за вас змінну, можна використати патерн деструктурування. Серед таких записів:
- Циклічна змінна циклів
for...in
for...of
іfor await...of
; - Параметри функції;
- Змінна зв'язування в
catch
.
В патернах присвоєння патерн не починається з ключового слова. Кожна деструктурована властивість присвоюється цілі присвоєння, котра може бути або оголошена заздалегідь з var
чи let
, або бути властивістю іншого об'єкта – загалом, може бути будь-чим, що може з'явитися зліва у виразі присвоєння.
const numbers = [];
const obj = { a: 1, b: 2 };
({ a: numbers[0], b: numbers[1] } = obj);
// Властивості `a` й `b` присвоюються властивостям `numbers`
[!NOTE] Дужки
( ... )
навколо інструкції присвоєння є обов'язковими, коли деструктурування присвоєння літерала об'єкта використовується без оголошення.
{ 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 | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Destructuring assignment
|
Chrome Full support 49 | Edge Full support 14 | Firefox Full support 41 | Internet Explorer No support Ні | Opera Full support 36 | Safari Full support 8 | 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 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 Ні | 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 Ні | 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 Ні | 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 |
Дивіться також
- Оператори присвоєння
- Заглиблення в ES6: Деструктурування на hacks.mozilla.org (2015)