Понятие деструктуризации
Деструктуризация - это разложение сложной структуры (объекта, массива, функции) на простые части (свойства, элементы, составляющие) в целях получения к ним доступа более эффективным способом.
Синтаксис деструктуризации без использования объемного (сложного) кода позволяет:
- получить доступ к элементам сложных объектов;
- управлять вложенными структурами объектов.
Обычно операции присваивания следуют шаблону target = source или target: source, но для деструктуризации действителен шаблон source: target (значение: переменная).
Рассмотрим пример:
1 2 3 4 5 6 |
const obj = { x: 4, y: 5, z: 6 }; // деструктуризация const { x: A, y: B, z: C } = obj; // объявляются переменные A, B, С и им присваиваются значения obj.x, obj.y, obj.z console.log(A, B, C); // 4, 5, 6 |
Если имя сопоставляемого свойства совпадает с именем переменной, вы можете использовать сокращение, например:
1 2 3 4 5 |
const { x: x, y: y, z: z } = obj; // равнозначно const { x, y, z } = obj; |
Возможности деструктуризации:
1 2 3 |
let [a, b, c] = "abc"; let [one, two, three] = new Set([1, 2, 3]); let [x, y, z] = [one, two, three]; |
С левой стороны деструктурирующего присваивания можно использовать любой объект, например:
1 2 3 |
let user = {}; [user.name, user.surname] = "Alex NAV".split(' '); console.log(user.name); // Alex |
1 2 3 |
let myObj = {}; ({ 0: myObj.one, 1: myObj.two, 2: myObj.three } = "abc"); console.log(myObj); // {one: 'a', two: 'b', three: 'c'} |
Деструктуризация объектов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
const person = { first: 'Alex', last: 'NAV', address: { city: 'Moscow' } }; // получение значений констант first и last из объекта person (простая деструктуризация) var { first, last } = person; // first = 'Alex', last = 'NAV' // деструктуризация с одновременным переименованием var { first: firstName, last: lastName } = person; // firstName= 'Alex', lastName = 'NAV' // деструктуризация со значением по умолчанию var { first, last, position = 'middle' } = person; // first = 'Alex', last = 'NAV', position = 'middle' // деструктуризация вложенных свойств var { first, last, address: { city } } = person; // first = 'Alex', last = 'NAV', city = 'Moscow' // деструктуризация вложенных свойств с одновременным переименованием var { first, last, address: { city: myCity } } = person; // first = 'Alex', last = 'NAV', myCity = 'Moscow' // деструктуризация с одновременной деструктуризацией вложенных свойств var { first, ...rest } = person; // first = 'Alex', rest = {last: 'NAV', address: {…}} |
Пусть имеется некоторый объект user:
1 2 3 4 5 6 7 8 9 |
const user = { firstName: "Alex", lastName: "NAV", age: 35, info: { adress: "Russia", skills: ["JavaScript", "teacher"] } }; |
Для получения значений свойств объекта, например, с ключами firstName и lastName без использования деструктуризации можно записать следующий код:
1 2 |
let firstName = user.firstName; let lastName = user.lastName; |
Тоже самое можно сделать, упростив код с помощью деструктуризации. Деструктурируем объект user и получим значения свойств firstName, lastName и age:
1 2 |
let { firstName, lastName, age } = user; // синтаксис деструктуризации console.log(firstName, lastName, age); // Alex NAV 35 |
где firstName, lastName, age - ключи соответствующих свойств деструктурируемого объекта user.
В приведенном выше примере мы использовали синтаксис деструктуризации для инициализации трех переменных (firstName, lastName и age) с присвоением значений из соответствующих свойств в объекте user.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const members = { Alex: { login: "loginAlex", age: 20 }, Serg: { login: "loginSerg", age: 10 }, Mary: { login: "loginMary", age: 30 }, Olga: { login: "loginOlga", age: 40 }, Margo: { login: "loginMargo", age: 30 }, }; // Функция выводит пользователей заданного возраста function selectionAge(object, setAge = 10) { for (let { login, age } of Object.values(object)) { // деструктуризация результата Object.values() при инициализации цикла if (age == setAge) { console.log(login, setAge); } } } selectionAge(members, 30); // loginMary 30 // loginMargo 30 |
Синтаксис деструктуризации может использоваться как при присвоении (назначении) переменных, так и при изменении их значений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Исходное состояние: инициализируем переменные firstName и lastName let firstName = "firstName"; let lastName = "lastName"; console.log(firstName, lastName); // firstName lastName // Где-то в коде инициализируем объект user const user = { firstName: "Alex", lastName: "NAV", age: 35, info: { adress: "Russia", skills: ["JavaScript", "teacher"] } }; // Выполним деструктуризацию и замену значений переменных firstName и lastName на значения соответствующих свойств объекта ({ firstName, lastName, age = 40 } = user); // age = 40 (по умолчанию), но если раскомментировать в объекте - приоритет свойства в объекте console.log(firstName, lastName, age); // Alex NAV 35 |
Отрывок кода выше показывает как использовать деструктуризацию объекта для назначения новых значений в переменные.
В выражении деструктуризации мы использовали пару закрывающих скобок ( ... ) , их отсутствие приведет к ошибке (блоки не могут находиться в левой стороне присвоения, так как JavaScript обрабатывает блок { ... } в основном потоке кода (не внутри другого выражения) как блок кода, например, для группировки операторов, а у нас - деструктуризация).
Пытаясь в выражении деструктуризации назначить переменную, соответствующую несуществующему в объекте ключу, мы получим значение undefined.
1 2 3 4 5 6 7 8 9 10 11 12 |
const user = { firstName: "Alex", lastName: "NAV", info: { adress: "Russia", skills: ["JavaScript", "teacher"] } }; // Выполним деструктуризацию и присвоим переменным значения свойств объекта user let { firstName, lastName, age } = user); // свойство age в объекте user отсутствует console.log(firstName, lastName, age); // Alex NAV undefined |
Для устранения этого положения переменной age необходимо передать значение по умолчанию (пример для объекта выше):
1 2 |
let { firstName, lastName, age = 40} = user; // age = 40 (по умолчанию), но если свойство будет в объекте - приоритет объекта console.log(firstName, lastName, age); // Alex NAV 40 |
JS предоставляет возможность определять имена переменных, отличные от именований ключей свойств деструктурируемого объекта, с использованием следующего синтаксиса:
{ [object_key] : [variable_name] }
или { [object_key] : [variable_name] = [default_value] }
1 2 3 4 5 6 7 8 9 10 |
let { firstName: name, lastName, age: years = 40 } = user; // переименуем firstName и age console.log(name, years); // Alex 35 // переменные firstName, age становятся недоступными и выдают ошибку console.log(firstName); // ReferenceError: firstName is not defined console.log(age); // ReferenceError: age is not defined // однако к свойствам объекта можно обратиться (их наименования не меняются): console.log(user.firstName); // Alex (ошибки нет) console.log(user.age); // 35 (ошибки нет) |
В приведенном выше примере созданы три переменных name, lastName, years, которые соответственно привязаны к ключам firstName, lastName, age свойств объекта user. Переменной years передано значение по умолчанию (40).
Используя деструктуризацию, мы можем получить доступ и к вложенным структурам объекта. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
const user = { firstName: "Alex", lastName: "NAV", age: 35, info: { adress: "Russia", skills: ["JavaScript", "teacher"] } }; let { info } = user; console.log(info); // Object { adress: "Russia", skills: (2) […] } // Доступ к свойству вложенного объекта let { info: { skills } } = user; console.log(skills); // Array [ "JavaScript", "teacher" ] // Запись смысла не имеет, к определению переменной не приведет (ошибку тоже не выдаст) let { info: { } } = user; |
Пустой вложенный литерал объекта в указанном выше примере при деструктуризации не делает никаких назначений (хотя ошибки тоже не выдаст).
Ещё пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const user = { name: "Alex", info: { children: ["Serg", "Olga"], cars: ["Lada", "Nissan", "BMW"], }, }; let getInfo = function (object = { name: "", info: {} }) { let { name = "Unknown", // значение по умолчанию info: { cars: [first, second] = [] }, // деструктуризация вложенного объекта } = object; console.log(`Name: ${name}`); console.log(`Cars: ${first}, ${second}`); }; getInfo(user); // Name: Alex, Cars: Lada, Nissan |
1 2 3 4 5 |
let array = []; ({ x: array[0], y: array[1], z: array[2] } = { x: 4, y: 5, z: 6 }); console.log(array); // [4, 5, 6] |
Деструктуризация массивов
Для синтаксиса деструктуризации массивов действителен шаблон:
[ значение[ i ] : именование ] = [ значение[ i ] ]
При деструктуризации массивов с левой стороны выражения назначения используется литерал массива, а значения переменных (элементов) в этом литерале массива соответствуют значениям элементов с таким же индексом в деструктурируемом массиве:
1 2 3 |
let a, b, c; console.log([a, b, c] = [1, 2, 3]); // [1, 2, 3] |
Или
1 2 3 |
let [a, b, c] = [1, 2, 3]; console.log([a, b, c]); // [1, 2, 3] |
Ещё пример:
1 2 3 4 5 6 7 8 9 10 |
// Исходный массив color const color = ["red", "blue", "yellow"]; // Деструктурируем массив color, переменной black для отсутствующего в массиве элемента передаётся значение по умолчанию "blank" let [red, bl, yel, black = "blank"] = color; console.log(red, yel, black); // red yellow blank let [, b, , bk = "blank"] = color; // можно пропустить ненужные элементы console.log(b, bk); // blue blank |
Особенности деструктуризации массивов:
- Если количество элементов в деструктурируемом массиве больше, чем количество переменных, инициализируемых в литерале нового массива, то лишние элементы деструктурируемого массива не учитываются.
- Если количество переменных, инициализируемых в литерале нового массива, превышает количество элементов в деструктурируемом массиве, то каждая лишняя переменная будет иметь значение undefined, кроме тех которым назначено значение по умолчанию.
- Возможно пропустить ненужные элементы массива и назначить переменным только те, которые необходимы.
1 2 3 4 5 |
let x = 10, y = 20; [y, x] = [x, y]; console.log(x, y); // 20 10 |
Ещё пример отсюда
Представьте, что вы делаете приложение манипуляций с фото и хотите менять размеры height и width при изменении вида с landscape на portrait.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let width = 300; let height = 400; const landscape = true; console.log(`${width} x ${height}`); // 300 x 400 if (landscape) { // Нужна дополнительная переменная, чтобы скопировать изначальную ширину let initialWidth = width; // Меняем значения переменных width = height; // высота теперь равна скопированному значению ширины height = initialWidth; console.log(`${width} x ${height}`); // 400 x 300 } |
Код выше решает задачу, хотя мы и использовали дополнительные переменные, чтобы скопировать значение одной из смененных переменных.
С помощью деструктуризации такой обмен осуществляется простым назначением:
1 2 3 4 5 6 7 8 9 |
let width = 300; let height = 400; const landscape = true; console.log(`${width} x ${height}`); // 300 x 400 if (landscape) { // Меняем значения переменных [width, height] = [height, width]; console.log(`${width} x ${height}`); // 400 x 300 } |
Для массивов доступна деструктуризация вложенных массивов. Деструктурируемый элемент должен быть массивом:
1 2 3 4 |
const members = ["Alex", ["NAV", "age 35"]]; let [, [log, age]] = members; // получение элементов вложенного массива console.log(log, age); // NAV age 35 |
Ещё пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const members = { 1: ["Alex", ["loginAlex", "ageAlex"]], 2: ["Serg", ["loginSerg", "ageSerg"]], 3: ["Olga", ["loginOlga", "ageOlga"]], 4: ["Mary", ["loginMary", "ageMary"]], }; function destruct(object) { for (let [, [userName, [login, age]]] of Object.entries(object)) { console.log(userName, login, age); } } destruct(members); // Alex loginAlex ageAlex // Serg loginSerg ageSerg // Olga loginOlga ageOlga // Mary loginMary ageMary |
Использование операторов rest и spread ( ... )
Читай также Операторы rest и spread ( … ) в JavaScript
Оператор … (rest или spread) используется по-разному, в зависимости от контекста применения:
- rest (остаток) - для соединения отдельных значений (элементов) в массив (объект) ( rest оператор всегда возвращает массив, а не псевдомассив);
- spread (распространение) - для разделения коллекций значений на отдельные элементы.
rest оператор в деструктурирующем приравнивании может располагаться только в конце выражения (перед закрывающей квадратной скобкой).
Оператор rest применяется, например, при назначении некоторых значений элементов массива переменным с одновременной передачей оставшихся элементов в массив:
1 2 3 4 5 6 7 |
const members = ["Alex", "login", "age"]; // исходный массив // Деструктурируем массив, получаем первый элемент и отправляем остальные в массив others const [firstNam, ...others] = members; // (rest собирает массив others) console.log(firstNam); // Alex console.log(others); // Array [ "NAV", "age 35" ] |
Получение клона массива с использованием оператора spread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const members = ["Alex", ["login", "age"]]; // исходный массив // Получение клона массива const [...newMembers] = members; // здесь деструктуризация // или const newMembers = [...members]; // здесь разложение массива members на элементы console.log(newMembers); // Array(3) [ "Alex", ["login", "age"]] (клон массива) // Проверка клонирования console.log(members === newMembers); // false members[1] = "Olga"; console.log("members: ", members); // members: Array [ "Alex", "Olga" ] console.log("newMembers: ", newMembers); // newMembers: Array [ "Alex", (2) […] ] |
Клонирование массива с добавлением (изменением) элементов:
1 2 3 4 5 |
// Добавление элемента const members = ["Alex", ["login", "age"]]; // исходный массив const newMembers = [...members, 'newAge']; // spread console.log(newMembers); // Array(3) [ "Alex", (2) […], "newAge" ] |
1 2 3 4 5 6 7 8 9 |
// Изменение значения элемента const user = { firstName: "Alex", lastName: "NAV", age: 35, info: { adress: "Russia", skills: ["JavaScript", "teacher"] } }; let newUser = { ...user, firstName: "Olga" }; // spread console.log(newUser); // Object { firstName: "Olga", lastName: "NAV", age: 35, info: {…} } |
[/spoiler]
Смешанная деструктуризация объектов
При работе со сложным объектом (массивом) для получения различных частей этой структуры можно использовать комбинации деструктуризации объектов и массивов. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Исходный объект const user = { firstName: "Alex", lastName: "NAV", age: 35, info: { adress: "Russia", skills: ["JavaScript", "teacher"] } }; // Деструктуризация const { info: { // получаем доступ к объекту свойства info skills: [js, teach] // получаем доступ к значениям вложенного свойства skills } } = user; console.log(js, teach); // JavaScript teacher |
Ещё пример:
1 2 3 |
const { 3: x, 4: y } = ['one', 'two', 'three', 'four', 'five']; console.log(x, y); // four five |
Деструктуризация при работе с аргументами функции
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
const user = { firstName: "Alex", lastName: "NAV", age: 35, info: { adress: "Russia", skills: ["JavaScript", "teacher"] } }; // Деструктуризация при передаче аргументов в функцию // если убрать ={} (объект по умолчанию), то выдаст ошибку TypeError: (destructured parameter) is undefined function callUser({ lastName = "", firstName = "", info: { skills } = {} } = {}) { console.log(lastName, firstName); // NAV Alex console.log(lastName, skills); // NAV Array [ "JavaScript", "teacher" ] } callUser(user); // если не передадим аргумент функции (объект) callUser(); // <empty string> <empty string> // <empty string> undefined |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Работа с деструктуризацией внутри функции const student = { name: 'John Doe', age: 16, scores: { maths: 74, english: 63, science: 85 } }; // С деструктуризацией function displaySummary({name, scores: { maths = 0, english = 0, science = 0 }}) { console.log('Hello, ' + name); console.log('Your Maths score is ' + maths); console.log('Your English score is ' + english); console.log('Your Science score is ' + science); } displaySummary(student); // Hello, John Doe // Your Maths score is 74 // Your English score is 63 // Your Science score is 85 |
Ещё пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const userMy = { name: "Olga", surname: "NAV", gender: "female", age: 25 }; function getInfo({ name, age, gender }) { return `${gender} name - ${name}; age - ${age}`; } let res = getInfo(userMy); // female name - Olga; age - 25 console.log(res); // или со значениями по умолчанию function getInfo({ name = "Alex", age = 40 }) { return `name - ${name}; age - ${age}`; } res = getInfo({}); // name - Alex; age - 40 console.log(res); // ВНИМАНИЕ! res = getInfo(); // TypeError: (destructured parameter) is undefined (параметр в виде объекта в функцию не передан) |
Создадим функцию quote(subject, config), обертывающую строку в кавычки, где
- subject - первый аргумент как строка, которую нужно обернуть;
- config - второй аргумент, объект со следующими свойствами:
- char — символ цитаты, например, ' (одинарная кавычка) или " (двойная кавычка); по умолчанию — ";
- skipIfQuoted — логическое значение для пропуска цитирования, если строка уже цитируется; по умолчанию — true.
Применяя преимущества деструктурирования объекта, давайте реализуем quote():
1 2 3 4 5 6 7 8 9 10 |
function quote(str, config) { const { char = '"', skipIfQuoted = false } = config; const length = str.length; if (skipIfQuoted && str[0] === char && str[length - 1] === char) { return str; } return char + str + char; } quote("Hello World", { char: "*" }); // => '*Hello World*' quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"' |
Код const { char = '"', skipIfQuoted = true } = config в одной строкe извлекает свойства char и skipIfQuoted из объекта config. Если некоторые свойства недоступны в объекте config, деструктурирование задает значения по умолчанию: '"' для char и false для skipIfQuoted.
Функцию можно улучшить, переместив деструктурирование прямо в раздел параметров, и установив значение по умолчанию (пустой объект { }) для параметра config, чтобы пропустить второй аргумент, когда будет достаточно значений по умолчанию.
1 2 3 4 5 6 7 8 9 |
function quote(str, { char = '"', skipIfQuoted = true } = {}) { const length = str.length; if (skipIfQuoted && str[0] === char && str[length - 1] === char) { return str; } return char + str + char; } quote("Hello World", { char: "*" }); // => '*Hello World*' quote('Sunny day'); // => '"Sunny day"' |
Обратите внимание, что деструктурирующее присваивание заменяет параметр config в сигнатуре функции. Это позволяет сделать параметр quote() на одну строку короче. Параметр = {} в правой части деструктурирующего присваивания гарантирует, что используется пустой объект, если второй аргумент не указан вообще (например, quote('Sunny day')).
Оператор rest можно использовать при работе с аргументами внутри функции:
1 2 3 4 5 6 7 8 9 10 |
var logNew = function(a, b, ...rest) { console.log(a, b, rest); }; logNew("Yoko", "Ono", "operator", "usage"); // Yoko Ono Array [ "operator", "usage" ] // или var logNewer = function(...args) { console.log(args); }; logNewer(1, 2, 3, 4, 5); // Array(5) [ 1, 2, 3, 4, 5 ] |
Последовательное присваивание при деструктуризации
Возвращаемое значение любого деструктурирующего выражения - это исходный объект (массив). Посмотрим, что это значит на практике:
1 2 3 4 5 |
let a, b, c, x, y, z; console.log({x, y, z } = { x: 4, y: 5, z: 6 }); // { x: 4, y: 5, z: 6 } console.log([a, b, c] = [1, 2, 3]); // [1, 2, 3] |
Благодаря переносу содержимого (значения) деструктурируемого объекта из выражения в выражение, вы можете создавать последовательности деструктурирующих выражений:
1 2 3 4 5 6 7 8 9 |
let a, b, c, x, y, z; [a, b] = [c] = [1, 2, 3]; console.log(a, b, c); // 1 2 1 ( {x} = {y,z} = {x: 4, y: 5, z: 6} ); console.log(x, y, z); // 4 5 6 |
Выражения [a, b] = [c] = [1, 2, 3] и ( {x} = {y,z} = {x: 4, y: 5, z: 6} ) вычисляются справа налево (т.е. возвращаемое значение [c] = [1, 2, 3] присваивается [a, b], а возвращаемое значение {y, z} = {x: 4, y: 5, z: 6} присваивается {x}.