Присвоєння з деструктуруванням
Синтаксис присвоєння з деструктуруванням — це вираз в 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, ...{ pop, push }] = [1, 2];
console.log(a, b); // 1 2
console.log(pop, push); // [Function pop] [Function push]
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 { a } = 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;
// self "123"
// prot "456" (Доступ до ланцюжка прототипів)
Специфікації
Сумісність із браузерами
desktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Destructuring assignment
|
Chrome Full support 49 | Edge Full support 14 | Firefox Full support 41 | 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 | 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 |