Деструктуризация в ES6. Полное руководство

Stas Bagretsov
12 min readMay 16, 2018

--

На данный момент JavaScript это самый используемый язык программирования в мире, используемый на разных платформах таких как веб-бразуеры, мобильные и VR устройства, а также веб-сервера. Хоть сам язык сильно не менялся за последние годы, в сравнении с другими языками программирования, все таки есть изменения, на которые стоит обратить внимание из-за явной “полезной силы”, добавленной в этот язык и тому примеры: шаблонные строки, деструктуризация, spread оператор, стрелочные функции и конечно же классы, ну и много чего другого интересного.

Адаптированный перевод статьи ES6 Destructuring: The Complete Guide

👉Мой Твиттер — там много из мира фронтенда, да и вообще поговорим🖖. Подписывайтесь, будет интересно: ) ✈️

В это руководстве мы сфокусируем внимание на освоении того, как приспособить “мощь” деструктуризации, чтобы упростить наш JavaScript код. Перед тем как погрузиться в детали того, как это всё работает, думаю сначала мы должны понять зачем нам надо это вообще делать. Если вы уже используете TypeScript или какой-нибудь современный JavaScript фреймворк, например React, Vue, Preact, Angular и т. п., то вы уже наверное знакомы с понятием деструктуризации. Однако, есть вероятность того, что прочитав это руководство, вы можете узнать что-нибудь новое, то о чем вы даже не подозревали.

Для прочтения этого материала подразумевается, что вы уже имели опыт написания кода на JavaScript. Есть шанс, что некоторые примеры кода могут не работать в вашем бразуере, всё будет зависеть от его версии, так как полностью весь функционал ES6 и ES7 пока что не поддерживается всеми доступными вариантами. Вы можете зайти и потестировать на Codepen, или в предпочитаемый вами любой онлайн редактор JavaScript. Ещё одной альтернативой может быть использование компилятора Babel, чтобы это точно сработало в вашем браузере.

Почему именно деструктуризация?

Чтобы это объяснить, мы рассмотрим сценарий с которым знакомо большинство, ну или хотя бы те, кто видел хоть мельком код на JavaScript. Представьте, что у нас есть данные студента, содержащие оценки по трем предметам: математика, естественные науки и английский, представленные в объекте данных. Нам нужно показать нужную информацию на основе этих данных. Для этого мы могли бы сделать, что-то типа такого:

const student = {name: 'John Doe',age: 16,scores: {maths: 74,english: 63,science: 85}};function displaySummary(student) {console.log('Hello, '+ student.name);console.log('Your Maths score is '+ (student.scores.maths || 0));console.log('Your English score is ' + (student.scores.english || 0));console.log('Your Science score is '+ (student.scores.science || 0));}displaySummary(student);// Hello, John Doe// Your Maths score is 74// Your English score is 63// Your Science score is 85

Используя код выше, мы бы достигли желаемого результата. Однако, есть несколько подводных камней в написании кода таким способом. Один из них — это то, что вы легко можете сделать ошибку или вместо написания scores.english, вы можете написать score.english, которое выдаст ошибку. Снова, если объект scores, к которому мы обращаемся, имеет глубокое вложение в виде другого объекта, цепочка обращения к объекту становится длиннее, что означает лишнюю писанину. Это может не показаться проблемой, но с помощью деструктуризации мы можем сделать тоже самое более выразительным и компактным синтаксисом.

Что такое деструктуризация?

Деструктуризация просто подразумевает разбивку сложной структуры на простые части. В JavaScript, таковая сложная структура обычно является объектом или массивом. Используя синтаксис деструктуризации, вы можете выделить маленькие фрагменты из массивов или объектов. Такой синтаксис может быть использован для объявления переменных или их назначения. Вы также можете управлять вложенными структурами, используя уже синтаксис вложенной деструктуризации.

Вообще используя деструктуризацию, функция из нашего предыдущего примера будет выглядеть так:

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);}

Есть шанс того, что код в последнем примере не совсем будет вами понят. Если это имеет место быть, то давайте разберемся с этим в руководстве ниже. Я вас уверяю, это имеет смысл.

И так, продолжим изучение того, как приручить мощь деструктуризации.

Деструктуризация объектов

То, что мы видели в последнем примере это форма деструктуризации объекта использованная как назначение функции. Давайте посмотрим, что мы можем сделать с деструктуризацией объекта, начиная с самого начала. Попросту, вы можете использовать объект литерал на левой стороне назначения выражения для деструктуризации объекта.

const student = {firstname: 'Glad',lastname: 'Chinda',country: 'Nigeria'};// Деструктурируем объектconst { firstname, lastname, country } = student;console.log(firstname, lastname, country); // Glad Chinda Nigeria

Тут мы использовали синтаксис деструктуризации, для назначения значений трех переменных: firstname, lastname и country, используя значения из соответствующих ключей в объекте student. Это была самая простая форма деструктуризации объекта.

Тот же синтаксис может использоваться в назначении переменных, как и тут:

// Инициализируем локальные переменныеlet country = 'Canada';let firstname = 'John';let lastname = 'Doe';
const student = {firstname: 'Glad',lastname: 'Chinda',country: 'Nigeria'};
// Переназначаем firstname и lastname с помощью деструктуризации// Прикрепляем скобки, так как это выражение назначения({ firstname, lastname } = student);
// Страна остается неизменной (Canada)console.log(firstname, lastname, country); // Glad Chinda Canada

Отрывок кода выше показывает как использовать деструктуризацию объекта для назначения новых значений в локальных переменных. Обратите внимание, что мы использовали пару закрывающих скобок () в выражении назначения. Если их пропустить, то деструктурируемый объект литерал будет восприниматься как блок, что приведет к ошибке, так как блоки не могут появляться в левой стороне назначения.

Дефолтные значения

Пытаясь назначить переменную, соответствующую ключу, которого не существует в деструктурированном объекте, вы получите значение undefined вместо назначения. Вы можете передать стандартные значения, которые будут назначены таким переменным, вместо undefined. Вот простой пример:

const person = {name: 'John Doe',country: 'Canada'};// Назначается дефолтное значение 25, если будет undefinedconst { name, country, age = 25 } = person;// Тут я использую шаблонные строки ES6console.log(`I am ${name} from ${country} and I am ${age} years old.`);// I am John Doe from Canada and I am 25 years old.’

Тут мы назначили дефолтное значение 25 для переменной age. Так как ключа age не существует в объекте person, то 25 назначится для переменной age, вместо undefined.

Использование разных имён для переменных

Пока что мы назначали переменные, которые имели те же имена, что и соответствующие им ключи. Вы можете назначить разные имена переменных, используя такой синтаксис [object_key]:[variable_name]. Вы также можете передать дефолтные значения, используя синтаксис: [object_key]:[variable_name] = [default_value]. Вот очень простой пример.

const person = {name: 'John Doe',country: 'Canada'};
// Назначаем дефолтное значения 25, если age undefinedconst { name: fullname, country: place, age: years = 25 } = person;
// Теперь щаблонные строки ES6console.log(`I am ${fullname} from ${place} and I am ${years} years old.`);// I am John Doe from Canada and I am 25 years old.’

Тут мы создали три локальных переменных по именам: fullname, place и years, которые привязаны к name, country и age ключам в объекте person. Обратите внимание, что мы указали дефолтное значение 25 для переменной years, в случае если ключ age будет отсутствовать в объекте person.

Деструктуризация вложенного объекта

Учитывая наш изначальный пример деструктуризации, мы помним, что объект scores вложен в student. Предположим, что мы хотим назначить Maths и Science оценки локальным переменным. Следующий код показывает как для этого мы можем использовать деструктуризацию вложенных объектов.

const student = {name: 'John Doe',age: 16,scores: {  maths: 74,  english: 63}};
// Определяем три локальных переменных: name, maths, scienceconst { name, scores: {maths, science = 50} } = student;console.log(`${name} scored ${maths} in Maths and ${science} in Elementary Science.`);// John Doe scored 74 in Maths and 50 in Elementary Science.

Тут мы определили три локальные переменные: name, maths и science. Также, мы указали дефолтное значение 50 для science в случае, если оно не существует во вложенном объекте scores. Обратите внимание, что scores не определен как переменная. Вместо этого мы используем вложенную деструктуризацию, чтобы извлечь maths и science значения из вложенного scores объекта.

Используя деструктуризацию вложенных объектов, будьте аккуратны в использовании пустых вложенных объектов литералов. Хоть это и верный синтаксис, он на самом деле не делает никаких назначений. Для примера, следующая деструктуризация абсолютно ничего не делает.

// Нет никаких назначений// Из-за пустого объект литерала ({})const { scores: {} } = student;

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

Вы наверное уже чувствуете себя ниндзей деструктуризации, пройдя через суровость деструктуризации объектов. Хорошие новости в том, что деструктуризация массивов очень схожа и приямолинейна с предыдущей. Давайте углубимся и в неё.

В деструктуризации массивов, вы используете литерал массива с левой стороны выражения назначения. Каждое имя переменной в литерале массива соответствует элементу с таким же индексом в деструктуризированном массиве. Вот быстрый пример.

const rgb = [255, 200, 0];// Деструктуризация массиваconst [red, green, blue] = rgb;console.log(`R: ${red}, G: ${green}, B: ${blue}`); // R: 255, G: 200, B: 0

В этом примере, мы назначили элементы в массиве rgb трём локальным переменным: red, green и blue, используя деструктуризацию массива. Обратите внимание, что каждая переменная отмечена соответствующим элементом с тем же индексом в rgb массиве.

Дефолтные значения

Если количество элементов в массиве больше, чем количество локальных переменных передавшихся литералу деструктуризирующегося массива, то избыточные элементы не учитываются. Но если количество локальных переменных передавшихся деструктуризирующемуся массиву превосходит количество элементов в массиве, то каждая лишняя переменная будет иметь значение undefined, кроме тех, кому вы не назначите дефолтное значение.

Как и с деструктуризацией объекта, вы можете выставить дефолтные значения для локальных переменных используя деструктуризацию массива. В следующем примере мы выставим дефолтные значения некоторым переменным на случай, если соответствующие элементы не будут найдены.

const rgb = [200];// Назначаем дефолтные значения 255 для red и blueconst [red = 255, green, blue = 255] = rgb;console.log(`R: ${red}, G: ${green}, B: ${blue}`); // R: 200, G: undefined, B: 255

Вы также можете сделать назначение деструктуризации массива. В отличие от деструктуризации объекта, вам не надо прикреплять выражение назначения в скобках. Вот пример:

let red = 100;let green = 200;let blue = 50;const rgb = [200, 255, 100];// Деструктуризирующее назначение для red и green[red, green] = rgb;console.log(`R: ${red}, G: ${green}, B: ${blue}`); // R: 200, G: 255, B: 50

Пропускаем элементы

Вполне возможно пропустить некоторые элементы, которые вы не хотите назначать локальным переменным и только назначить те, в которые вам нужны. Вот пример того, как мы можем назначить только значение blue для локальной переменной.

const rgb = [200, 255, 100];// Пропустите первые два элемента// Назначьте только третий элемент переменной blue
const [,, blue] = rgb;console.log(`Blue: ${blue}`); // Blue: 100

Обмен переменными

Одно очень хорошее примечание в деструктуризации массивов заключается в обмене локальными переменными. Представьте, что вы делаете приложение манипуляций с фото и хотите менять height и width размеры для них, при ориентировке фото измененного в landscape и portrait. Старый способ сделать это будет выглядеть примерно так:

let width = 300;let height = 400;const landscape = true;console.log(`${width} x ${height}`); // 300 x 400if (landscape) {// Нужна дополнительная переменная, чтобы скопировать изначальную ширинуlet initialWidth = width;
// Меняем значения перменныхwidth = height;
// высота теперь равна скопированному значению шириныheight = initialWidth;console.log(`${width} x ${height}`); // 400 x 300}

Код выше решает задачу, хотя мы и использовали дополнительные переменные, чтобы скопировать значение одной из смененных переменных. С помощью деструктуризации, мы можем проделать такой обмен простым назначением. Следующий код покажет, как с помощью деструктуризации можно это провернуть:

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}

Деструктуризация вложенного массива

Так же, как и с объектами, вы можете проделать вложенную деструктуризацию с массивами. Соответствующий элемент, должен быть массивом, чтобы использовать вложенный деструктуризующий массив литерал для назначения элементов их локальным переменным. Вот быстрый пример, чтобы это показать:

const color = ['#FF00FF', [255, 0, 255], 'rgb(255, 0, 255)'];
// Используем вложенную деструктуризацию назначая red, green и blueconst [hex, [red, green, blue]] = color;console.log(hex, red, green, blue); // #FF00FF 255 0 255

В коде выше, мы назначили hex переменную и использовали деструктуризацию вложенного массива, чтобы назначить red, green и blue переменные.

Элементы rest

Иногда вы можете захотеть назначить некоторые элементы переменным, удостоверясь, что оставшиеся элементы назначены другим локальным переменным. Новый оператор расширения (…) был добавлен в ES6, он может быть использован с деструктуризацией, чтобы достигнуть описанного выше эффекта. Вот быстрый пример:

const rainbow = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];// Назначаем первый и третий элементы на red и yellow// Оставшиеся элементы назначаем для otherColors переменной, используя spread-операторconst [red,, yellow, …otherColors] = rainbow;console.log(otherColors); // ['green', 'blue', 'indigo', 'violet']

Тут вы видите, что после назначения первого и третьего элемента массива rainbow для red и yellow, мы использовали оператор расширения (…), чтобы зафиксировать и назначить оставшиеся значения переменной otherColors. Это отсылка к переменной оставшихся значений. Обратите внимание, что переменные оставшихся элементов, при использовании должны всегда появляться в конце элемента в деструктуризирующемся массиве, в противном случае будет выдана ошибка.

Есть некоторые хорошие примечания для rest элементов, которые стоит рассмотреть. Одно из таких это клонирование массивов.

Клонирование массивов

В JavaScript, массивы являются присваевымыми типами и следовательно они назначаются присваиванием вместо копирования. Таким образом, в следующем примере, как с rainbow, так и rainbowClone переменные указывают на один и тот же массив в памяти и следовательно, любое изменение сделанное в rainbow, будет применено для rainbowClone и наоборот.

const rainbow = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];const rainbowClone = rainbow;// как rainbow, так и rainbowClone ссылаются // на тот же массив в памяти, следовательно выдавая trueconsole.log(rainbow === rainbowClone); // true
// Оставляем только три элемента и оставляем оставшеесяrainbowClone.splice(3);
// Получаем:console.log(rainbow); // ['red', 'orange', 'yellow']console.log(rainbowClone); // ['red', 'orange', 'yellow']Следующий пример кода показывает, как мы можем клонировать массив старым методом — Array.prototype.slice и Array.prototype.concat на всякий случай.const rainbow = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];// Клонируем с помощью Array.prototype.sliceconst rainbowClone1 = rainbow.slice();console.log(rainbow === rainbowClone1); // falseconsole.log(rainbowClone1); // ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']// Клонируем с помощью Array.prototype.concatconst rainbowClone2 = rainbow.concat();console.log(rainbow === rainbowClone2); // falseconsole.log(rainbowClone2); // ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']

А вот как вы можете использовать деструктуризацию и оператор расширения для создания клона массива.

const rainbow = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];// Клонируем с деструктуризацией и оператором расширенияconst […rainbowClone] = rainbow;console.log(rainbow === rainbowClone); // falseconsole.log(rainbowClone); // ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']

Смешанная деструктуризация

Есть случаи, когда вы работаете с довольно сложным объектом/массивом и вам надо назначить некоторые значения от них для локальных переменных. Хорошим примером тут будет объект с несколькими вложенными объектами и массивами. В случаях как тут, вы можете использовать комбинации деструктуризации объектов и массивов, чтобы нацелиться на определенные части этой комплексной структуры. Следующий код покажет такой пример:

const person = {name: 'John Doe',age: 25,location: {country: 'Canada',city: 'Vancouver',coordinates: [49.2827, -123.1207]}}
// Посмотрите как смешанная деструктуризация используется// Мы назначаем 5 переменных: name, country, lat, lngconst {name, location: {country, city, coordinates: [lat, lng]}} = person;console.log(`I am ${name} from ${city}, ${country}. Latitude(${lat}), Longitude(${lng})`);// I am John Doe from Vancouver, Canada. Latitude(49.2827), Longitude(-123.1207)

Деструктуризированные параметры функции

Деструктуризация также может применяться на параметрах функции, чтобы извлекать значения и назначать их локальным переменным. Обратите внимание, что деструктурированный параметр не может быть пропущен, в противном случае это выдаст ошибку.

Хороший пример использования тут — это displaySummary() функция из нашего изначального примера, которая ожидает объект student как параметр. Мы может деструктуризировать объект student и назначить извлеченное значение локальной переменной функции. Вот пример:

const student = {name: 'John Doe',age: 16,scores: {maths: 74,english: 63,science: 85}};
// Без деструктуризацииfunction displaySummary(student) {console.log('Hello, ' + student.name);console.log('Your Maths score is ' + (student.scores.maths || 0));console.log('Your English score is ' + (student.scores.english || 0));console.log('Your Science score is ' + (student.scores.science || 0));}// С деструктуризацией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);

Тут мы ожидали значения, которые нам нужны из объекта student и назначали их локальным переменным: name, maths, english и science. Обратите внимание, что хоть мы указывали дефолтное значение для некоторых перменных, то если вы вызовите функцию без аргументов, вы получите ошибку, так как деструктурированные параметры всегда нужны. Вы можете назначить фолбэк объект как дефолтное значение для объекта student и вложенный объект scores на случай, если он не будет указан, чтобы избежать ошибки в следующем примере.

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);}// Вызываем без аргумента studentdisplaySummary();// Hello, undefined// Your Maths score is 0// Your English score is 0// Your Science score is 0

Заключение

В этом руководстве, мы изучили синтаксис деструктуризации в ES6 и различные способы его применения в коде. Если вы уже довольно хорошо знакомы с деструктуризацией в JavaScript, то вы возможно узнали что-то новое. Хоть мы и использовали оператор расширения в всего нескольких случаях, стоит помнить, что с его помощью можно сделать много всего интересного.

--

--

Stas Bagretsov
Stas Bagretsov

Written by Stas Bagretsov

Надеюсь верую вовеки не придет ко мне позорное благоразумие. webdev/sports/books