Сравниваем традиционный способ извлечения значений и деструктуризацию (ES6) в JavaScript
В этой статье мы рассмотрим традиционное присваивание значений из объектов в переменные и новый синтаксис деструктуризации в ES6. Материал адаптирован на русский язык совместно с тимлидом группы frontend-разработки в Skillbox Иваном Казанцевым.
Деструктуризация — это синтаксис, который позволяет распаковывать значения из массивов или свойства из объектов в переменные.
Что нам это дает? Возможность писать более чистый и понятный код, экономя время и количество строк.
Деструктуризация объектов
Сначала рассмотрим деструктуризацию объектов. И для примера возьмем объект - клиент (customer).
const customer = {
name: 'Sherlock',
email: 's.h.@gmail.com',
age: 34,
address: {
streetNo: '221b Baker Street',
city: 'London',
country: 'UK'
}
}
Базовое присвоение переменной
Если бы нам нужно было достать из него name и email, привычным способом мы бы сделали так:
const name = customer.name;
const email = customer['email']; // или так
console.log(name); // Sherlock
console.log(email); // s.h@gmaill.com
С помощью деструктуризации мы можем делать так:
const { name, email } = customer;
console.log(name); // Sherlock
console.log(email); // s.h@gmaill.com
Как минимум, мы уже не пишем два раза customer и чувствуем себя чуть-чуть лучше, так как можем добавить в резюме что-то про DRY.
Но если мы хотим записать customer.name
в переменную с другим именем, выходит, снова нужен традиционный способ? Нет, это мы тоже можем сделать:
const { name: customerName, email } = customer;
// обратите внимание, имя свойства из объекта customer находится слева
console.log(customerName); // Sherlock
Присвоение значений объявленным переменным
Если мы хотим присвоить значения уже существующим переменным, делаем почти тоже самое, но добавляем ()
.
let name, email;
({ name, email } = customer);
console.log(name);
console.log(email);
Почему мы добавляем ()
?
{
в левой части считается блоком, а не литералом объекта. Если опустите ()
, вы получите сообщение об ошибке:
{ name, email } = customer;
^SyntaxError: Unexpected token '='
Вложенные объекты
Итак, мы поняли, что это удобно, можем усложнять. Если хотим получить доступ к свойствам city
и country
, в традиционном способе нам нужно использовать цепочку обращений:
const name = customer.name;
const streetNo = customer.address.streetNo;
const city = customer['address']['city']; // или так
console.log(name); // Sherlock
console.log(streetNo); // 221b Baker Street
console.log(city); // London
Мы можем сделать то же самое, но короче, используя деструктуризацию:
const { name, address: { streetNo, city } } = customer;
console.log(name); // Sherlock
console.log(streetNo); // 221b Baker Street
console.log(city); // UK
Возможно, выглядит сложнее, но это дело привычки. И да, мы все еще можем записать свойства streetNo
и city
в переменные с другим названием.
Значение по умолчанию
Идем дальше. Теперь перед нами стоит задача: добавить married (женат/замужем) нашему объекту customer
. Возможные варианты ответа: “да” (yes) или “нет” (no). В случае, если не указан, присвоить значение “нет ответа” (N/A).
Без деструктуризации мы бы сделали так:
let married = customer.married;
console.log(married); // undefined
if (!married) {
married = 'N/A';
}
console.log(married); // N/A
Но с деструктуризацией мы можем объявлять значения по умолчанию, тем самым избавиться от условия. Фантастика?
const { name, married = 'N/A' } = customer;
console.log(name); // Sherlock
console.log(married); // N/A
Остаточные параметры объекта
Представим, что нам нужно достать только name
( например, чтобы присвоить ему значение по умолчанию), а все остальное записать “как есть” в другую переменную.
const { name = 'Спрятался в коробке', ...rest } = customer;
console.log(name); // Sherlock
console.log(rest);
// {
// email: 's.h@gmaill.com',
// age: null,
// address: { streetNo: '221b Baker Street', city: 'London', country: 'UK' }
// }
Обратите внимание, вместо rest
вы можете использовать любое другое название переменной, просто не забудьте поставить ...
и расположить это в конце.
Деструктуризация null
Стоит иметь ввиду, чтоnull
деструктурировать нельзя, мы получим ошибку:
function getCustomer() {
return null;
}
let { name, email } = getCustomer();
// TypeError: Cannot destructure property name of 'undefined' or 'null'.
Поэтому мы должны обрабатывать такие случаи, например, так:
let { name = 'Спрятался в коробке', email = null } = getCustomer() || {};
console.log(name, email); // Спрятался в коробке null
Деструктуризация аргументов функции
Попробуем использовать деструктуризацию для упрощения работы с аргументами функции? Давайте представим, что нам нужна функция, которая выводит в консоль имя и город клиента.
Как бы мы сделали ранее:
let display = (customer) => console.log(`${customer.name} ${customer.address.city}`);
display(customer); // Sherlock London
Как мы сделаем теперь:
let display = ( { name, address: { city } } ) => console.log(`${name} ${city}`);
display(customer); // Sherlock London
Деструктуризация массивов
Да, это работает и с массивами. Для примера нам понадобится пакет с фруктами.
const fruits = ['Banana', 'Orange', 'Apple', 'Mango'];
Базовая деструктуризация массива
Если бы нам нужно было достать банан и апельсин, ранее мы бы сделали следующее:
let firstFruit = fruits[0];
let secondFruit = fruits[1];
console.log(firstFruit); // Banana
console.log(secondFruit); // Orange
Но не сегодня, сегодня мы познакомились с деструктуризацией!
let [firstFruit, secondFruit, thirdFruit] = fruits;
console.log(firstFruit); // Banana
console.log(secondFruit); // Orange
console.log(thirdFruit); // Apple (да, мы можем позволить себе и третий фрукт, потому что мы молодцы)
Пропуск пунктов
В объектах мы могли доставать любые значения свойств по имени и записывать их в переменные. А что, если мы хотим достать только первый и третий фрукт из массива? Нам просто нужно добавить лишнюю запятую.
let [firstFruit, , thirdFruit] = fruits;
Да-да, мы не указываем название переменной, и второй элемент пропускается.
Остаточные параметры массива
И в массивах мы тоже можем использовать остаточные параметры. Например, мы хотим достать первый фрукт себе, второй оставить в пакете, а все остальное отдать друзьям:
let [yourFav, , ...friendsFav] = fruits;
console.log(yourFav); // Banana
console.log(friendsFav); // [ 'Apple', 'Mango' ]
Замена значений
И в конце — фокус. Сейчас мы поменяем местами первый и второй фрукт, не объявляя для этого новую переменную.
let [firstFruit, secondFruit] = fruits;
console.log(firstFruit); // Banana
console.log(secondFruit); // Orange
[firstFruit, secondFruit] = [secondFruit, firstFruit]
console.log(firstFruit); // Orange
console.log(secondFruit); // Banana
Ловкость деструктуризации и никакого мошенничества.
Ну а на сегодня все. Надеюсь, после этой статьи вам удастся упростить свой код и сделать его более читаемым.
Комментарии (20)
mikhanoid
29.03.2022 17:48-6Это называется "деконструкция"
glebmachine
29.03.2022 19:01+2Говорят и так и так, но правильнее деструктуризация, даже гугл с вами не согласится)
https://www.google.com/search?q=деконструкция+объекта&sxsrf=APq-WBuBqd6rRjMxREJjsu_vrl53sdXwcA%3A1648569589881&ei=9SxDYummNd2n9u8P_5KVuAo&ved=0ahUKEwip9aqm2Ov2AhXdk_0HHX9JBacQ4dUDCA4&uact=5&oq=деконструкция+объекта&gs_lcp=Cgdnd3Mtd2l6EAMyBAgAEEcyBAgAEEcyBAgAEEcyBAgAEEcyBAgAEEcyBAgAEEcyBAgAEEcyBAgAEEc6BwgjELADECc6BwgAEEcQsANKBAhBGABKBAhGGABQxAxYxAxgvxxoAnACeACAAQCIAQCSAQCYAQCgAQHIAQjAAQE&sclient=gws-wizmikhanoid
31.03.2022 00:31-1В чём Вы видите несогласие? Термин "деструктуризация" упомянут в двух учебниках по JS. В большинстве случаев употребляется термин деконструкция.
Речь идёт не о процессе лишения чего-то структуры (например, деструктуризация власти; сравните с реструктуризацией), а о процессе, обратном конструированию (например, деконструкция моста; сравните с реконструкцией). В теории языков программирования те процедуры, которые разбирают данные на части называют деконструкторами, в противоположность конструкторам (термин "деструктор" уже был занят). На английском, ведь, то же самое:https://duckduckgo.com/?q=destructure+tuple
https://duckduckgo.com/?q=deconstruct+tupleВ JS речь идёт конкретно о destructuring assignment - о присваивании, забывающем структуру, но сам процесс разбора объекта на отдельные составляющие - это, всё же, деконструкция.
Sadler
29.03.2022 20:50+2Во-первых, я бы не назвал ES6 "новым".
Во-вторых, слепое деструктурирование всего и вся очень быстро может привести к стрельбе себе в ногу, т.к., если уж взялись деструктурировать, всегда следует точно понимать, где вам необходима передача by reference, а где достаточно копирования by value.
В-третьих, также не забываем, если вы собираетесь использовать деструктурирование, особенно вместе с инициализаторами и spread, это не бесплатные операции. Без понимания этого люди пишут эпически тормозной код.
wifftees
29.03.2022 23:38-4В ином случае они бы писали бы эпически нечитабельный код. Так что из двух зол меньшее.
Format-X22
30.03.2022 02:43+1Уже 7 лет как es6 вышел как официальный стандарт, 8 лет как лично я осознанно использовал его бетта-фичи, 9 лет как хром его частично реализовал, но ради кармы и, видимо, денег всё пишут и пишут о “новом синтаксесе es6”.
amakhrov
30.03.2022 03:27-1А что именно в деструктурировании тормозное?
const {name} = object const name2 = object.name
у этих двух вызовов будет разница в скорости?
Sadler
30.03.2022 04:13Нет, в таком случае никакой разницы в скорости не будет. А вот с rest-ом для длинных массивов я был бы более осторожен, т.к. это, как минимум, slice, т.е. копирование части массива в памяти. Если мы говорим не только о деструктурировании, но и о spread, который часто используют с ним в паре, то мне приходит в голову вот такой антипаттерн:
Пожалуйста, не делайте так
[1,2,3,4,5].reduce((acc, value, index) => ({...acc, [value]: index}), {});
amakhrov
30.03.2022 04:30Понял. Все же дополнительные операции не в деструктуризации как таковой, а именно в rest. Но если кто-то делает rest с деструктуризацией, то предполагается, что ему таки нужно это значение.
Кстати, ваш антипаттерн - он про spread syntax, тут вообще нет деструктуризации.
Sadler
30.03.2022 04:31Кстати, ваш антипаттерн - он про spread syntax, тут вообще нет деструктуризации.
Я так и сказал. Сорри, если неточно выразился, надо, видимо, иногда спать... :)
RumataEstora
30.03.2022 08:15С одной стороны как-то сложно и запутанно, и автор сам это отмечает. С другой стороны - подобные фичи в том или ином объеме представлены в других языках, например, Perl и его последователях.
scorpion1574
30.03.2022 09:10У автора мелкая ошибка в подглаве "Базовая деструктуризация массива".
console.log(secondFruit); // Orange console.log(secondFruit); // Apple (да, мы можем позволить себе и третий фрукт, потому что мы молодцы)
Timofeuz
30.03.2022 12:09так как можем добавить в резюме что-то про DRY.
По-моему DRY немного не про это..
Кстати, а можно смаппить из объекта только свойства, имеющиеся в записывемом? Типа
let obj = {name: "Tmfs"} let source = {name: "John", age:25}; obj = ... // копируем только name
Rsa97
IMHO, сейчас лучше использовать ??, а не
||
savostin
А не подскажите какой плагин для VSCode "понимает" Nullish? А то
JS-CSS-HTML Formatterjs-beautify нагло вносит кашу и ломает код. Ну или как его настроить...Sadler
Prettier пробовали?
savostin
Оказывается надо было принудительно обновить js-beautify внутри JS-CSS-HTML Formatter
i1kazantsev
Соглашусь. Но, кмк, в рамках статьи про одну из фичей es6 это бы добавило вопросов к примеру, поэтому в таком контексте я все-таки согласен с примером оригинала.
Sergey_smu
IMHO, || и ?? таки для разных целей. ?? может вернуть 0 или пустую строку, в отличии от. Так как || проверяет на истинность, а ?? на присвоение значение(всё, что не null || undefined).
mayorovp
И именно по этой причине надо привыкать использовать ?? для значений по умолчанию.