Сравниваем традиционный способ извлечения значений и деструктуризацию (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)


  1. Rsa97
    29.03.2022 17:40
    +4

    let { name = 'Спрятался в коробке', email = null } = getCustomer() || {};

    IMHO, сейчас лучше использовать ??, а не ||


    1. savostin
      29.03.2022 20:35

      А не подскажите какой плагин для VSCode "понимает" Nullish? А то JS-CSS-HTML Formatter js-beautify нагло вносит кашу и ломает код. Ну или как его настроить...


      1. Sadler
        29.03.2022 21:00
        +1

        Prettier пробовали?


      1. savostin
        29.03.2022 21:04
        +1

        Оказывается надо было принудительно обновить js-beautify внутри JS-CSS-HTML Formatter


    1. i1kazantsev
      29.03.2022 23:37

      Соглашусь. Но, кмк, в рамках статьи про одну из фичей es6 это бы добавило вопросов к примеру, поэтому в таком контексте я все-таки согласен с примером оригинала.


    1. Sergey_smu
      29.03.2022 23:37
      -1

      IMHO, || и ?? таки для разных целей. ?? может вернуть 0 или пустую строку, в отличии от. Так как || проверяет на истинность, а ?? на присвоение значение(всё, что не null || undefined).


      1. mayorovp
        30.03.2022 00:29
        +3

        И именно по этой причине надо привыкать использовать ?? для значений по умолчанию.


  1. mikhanoid
    29.03.2022 17:48
    -6

    Это называется "деконструкция"


    1. glebmachine
      29.03.2022 19:01
      +2

      1. mikhanoid
        31.03.2022 00:31
        -1

        В чём Вы видите несогласие? Термин "деструктуризация" упомянут в двух учебниках по JS. В большинстве случаев употребляется термин деконструкция.

        Речь идёт не о процессе лишения чего-то структуры (например, деструктуризация власти; сравните с реструктуризацией), а о процессе, обратном конструированию (например, деконструкция моста; сравните с реконструкцией). В теории языков программирования те процедуры, которые разбирают данные на части называют деконструкторами, в противоположность конструкторам (термин "деструктор" уже был занят). На английском, ведь, то же самое:

        https://duckduckgo.com/?q=destructure+tuple
        https://duckduckgo.com/?q=deconstruct+tuple

        В JS речь идёт конкретно о destructuring assignment - о присваивании, забывающем структуру, но сам процесс разбора объекта на отдельные составляющие - это, всё же, деконструкция.


  1. Sadler
    29.03.2022 20:50
    +2

    Во-первых, я бы не назвал ES6 "новым".

    Во-вторых, слепое деструктурирование всего и вся очень быстро может привести к стрельбе себе в ногу, т.к., если уж взялись деструктурировать, всегда следует точно понимать, где вам необходима передача by reference, а где достаточно копирования by value.

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


    1. wifftees
      29.03.2022 23:38
      -4

      В ином случае они бы писали бы эпически нечитабельный код. Так что из двух зол меньшее.


    1. Format-X22
      30.03.2022 02:43
      +1

      Уже 7 лет как es6 вышел как официальный стандарт, 8 лет как лично я осознанно использовал его бетта-фичи, 9 лет как хром его частично реализовал, но ради кармы и, видимо, денег всё пишут и пишут о “новом синтаксесе es6”.


    1. amakhrov
      30.03.2022 03:27
      -1

      А что именно в деструктурировании тормозное?

      const {name} = object
      const name2 = object.name

      у этих двух вызовов будет разница в скорости?


      1. Sadler
        30.03.2022 04:13

        Нет, в таком случае никакой разницы в скорости не будет. А вот с rest-ом для длинных массивов я был бы более осторожен, т.к. это, как минимум, slice, т.е. копирование части массива в памяти. Если мы говорим не только о деструктурировании, но и о spread, который часто используют с ним в паре, то мне приходит в голову вот такой антипаттерн:

        Пожалуйста, не делайте так

        [1,2,3,4,5].reduce((acc, value, index) => ({...acc, [value]: index}), {});


        1. amakhrov
          30.03.2022 04:30

          Понял. Все же дополнительные операции не в деструктуризации как таковой, а именно в rest. Но если кто-то делает rest с деструктуризацией, то предполагается, что ему таки нужно это значение.

          Кстати, ваш антипаттерн - он про spread syntax, тут вообще нет деструктуризации.


          1. Sadler
            30.03.2022 04:31

            Кстати, ваш антипаттерн - он про spread syntax, тут вообще нет деструктуризации.

            Я так и сказал. Сорри, если неточно выразился, надо, видимо, иногда спать... :)


  1. RumataEstora
    30.03.2022 08:15

    С одной стороны как-то сложно и запутанно, и автор сам это отмечает. С другой стороны - подобные фичи в том или ином объеме представлены в других языках, например, Perl и его последователях.


  1. scorpion1574
    30.03.2022 09:10

    У автора мелкая ошибка в подглаве "Базовая деструктуризация массива".

    console.log(secondFruit); // Orange
    console.log(secondFruit); // Apple (да, мы можем позволить себе и третий фрукт, потому что мы молодцы)


  1. Timofeuz
    30.03.2022 12:09

    так как можем добавить в резюме что-то про DRY.

    По-моему DRY немного не про это..

    Кстати, а можно смаппить из объекта только свойства, имеющиеся в записывемом? Типа

    let obj = {name: "Tmfs"}
    let source = {name: "John",
                  age:25};
    
    obj = ... // копируем только name