1 августа 2019 г.

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

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Более новая информация по этой теме находится на странице https://learn.javascript.ru/destructuring-assignment.

Деструктуризация (destructuring assignment) – это особый синтаксис присваивания, при котором можно присвоить массив или объект сразу нескольким переменным, разбив его на части.

Массив

Пример деструктуризации массива:

'use strict';

let [firstName, lastName] = ["Илья", "Кантор"];

alert(firstName); // Илья
alert(lastName);  // Кантор

При таком присвоении первое значение массива пойдёт в переменную firstName, второе – в lastName, а последующие (если есть) – будут отброшены.

Ненужные элементы массива также можно отбросить, поставив лишнюю запятую:

'use strict';

// первый и второй элементы не нужны
let [, , title] = "Юлий Цезарь Император Рима".split(" ");

alert(title); // Император

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

Оператор «spread»

Если мы хотим получить и последующие значения массива, но не уверены в их числе – можно добавить ещё один параметр, который получит «всё остальное», при помощи оператора "..." («spread», троеточие):

'use strict';

let [firstName, lastName, ...rest] = "Юлий Цезарь Император Рима".split(" ");

alert(firstName); // Юлий
alert(lastName);  // Цезарь
alert(rest);      // Император,Рима (массив из 2х элементов)

Значением rest будет массив из оставшихся элементов массива. Вместо rest можно использовать и другое имя переменной, оператор здесь – троеточие. Оно должно стоять только последним элементом в списке слева.

Значения по умолчанию

Если значений в массиве меньше, чем переменных – ошибки не будет, просто присвоится undefined:

'use strict';

let [firstName, lastName] = [];

alert(firstName); // undefined

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

'use strict';

// значения по умолчанию
let [firstName="Гость", lastName="Анонимный"] = [];

alert(firstName); // Гость
alert(lastName);  // Анонимный

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

'use strict';

function defaultLastName() {
  return Date.now() + '-visitor';
}

// lastName получит значение, соответствующее текущей дате:
let [firstName, lastName=defaultLastName()] = ["Вася"];

alert(firstName); // Вася
alert(lastName);  // 1436...-visitor

Заметим, что вызов функции defaultLastName() для генерации значения по умолчанию будет осуществлён только при необходимости, то есть если значения нет в массиве.

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

Деструктуризацию можно использовать и с объектами. При этом мы указываем, какие свойства в какие переменные должны «идти».

Базовый синтаксис:

let {var1, var2} = {var1: …, var2: …};

Объект справа – уже существующий, который мы хотим разбить на переменные. А слева – список переменных, в которые нужно соответствующие свойства записать.

Например:

'use strict';

let options = {
  title: "Меню",
  width: 100,
  height: 200
};

let {title, width, height} = options;

alert(title);  // Меню
alert(width);  // 100
alert(height); // 200

Как видно, свойства options.title, options.width и options.height автоматически присвоились соответствующим переменным.

Если хочется присвоить свойство объекта в переменную с другим именем, например, чтобы свойство options.width пошло в переменную w, то можно указать соответствие через двоеточие, вот так:

'use strict';

let options = {
  title: "Меню",
  width: 100,
  height: 200
};

let {width: w, height: h, title} = options;

alert(title);  // Меню
alert(w);      // 100
alert(h);      // 200

В примере выше свойство width отправилось в переменную w, свойство height – в переменную h, а title – в переменную с тем же названием.

Если каких-то свойств в объекте нет, можно указать значение по умолчанию через знак равенства =, вот так;

'use strict';

let options = {
  title: "Меню"
};

let {width=100, height=200, title} = options;

alert(title);  // Меню
alert(width);  // 100
alert(height); // 200

Можно и сочетать одновременно двоеточие и равенство:

'use strict';

let options = {
  title: "Меню"
};

let {width:w=100, height:h=200, title} = options;

alert(title);  // Меню
alert(w);      // 100
alert(h);      // 200

А что, если в объекте больше значений, чем переменных? Можно ли куда-то присвоить «остаток», аналогично массивам?

Такой возможности в текущем стандарте нет. Она планируется в будущем стандарте, и выглядеть она будет примерно так:

'use strict';

let options = {
  title: "Меню",
  width: 100,
  height: 200
};

let {title, ...size} = options;

// title = "Меню"
// size = { width: 100, height: 200} (остаток)

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

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

В примерах выше переменные объявлялись прямо перед присваиванием: let {…} = {…}. Конечно, можно и без let, использовать уже существующие переменные.

Однако, здесь есть небольшой «подвох». В JavaScript, если в основном потоке кода (не внутри другого выражения) встречается {...}, то это воспринимается как блок.

Например, можно использовать такой блок для ограничения видимости переменных:

'use strict';
{
  // вспомогательные переменные, локальные для блока
  let a = 5;
  // поработали с ними
  alert(a); // 5
  // больше эти переменные не нужны
}
alert(a); // ошибка нет такой переменной

Конечно, это бывает удобно, но в данном случае это создаст проблему при деструктуризации:

let a, b;
{a, b} = {a:5, b:6}; // будет ошибка, оно посчитает, что {a,b} - блок

Чтобы избежать интерпретации {a, b} как блока, нужно обернуть всё присваивание в скобки:

let a, b;
({a, b} = {a:5, b:6}); // внутри выражения это уже не блок

Вложенные деструктуризации

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

Деструктуризации можно как угодно сочетать и вкладывать друг в друга.

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

'use strict';

let options = {
  size: {
    width: 100,
    height: 200
  },
  items: ["Пончик", "Пирожное"]
}

let { title="Меню", size: {width, height}, items: [item1, item2] } = options;

// Меню 100 200 Пончик Пирожное
alert(title);  // Меню
alert(width);  // 100
alert(height); // 200
alert(item1);  // Пончик
alert(item2);  // Пирожное

Как видно, весь объект options корректно разбит на переменные.

Итого

  • Деструктуризация позволяет разбивать объект или массив на переменные при присвоении.

  • Синтаксис:

    let {prop : varName = default, ...} = object

    Здесь двоеточие : задаёт отображение свойства prop в переменную varName, а равенство =default задаёт выражение, которое будет использовано, если значение отсутствует (не указано или undefined).

    Для массивов имеет значение порядок, поэтому нельзя использовать :, но значение по умолчанию – можно:

    let [var1 = default, var2, ...rest] = array

    Объявление переменной в начале конструкции не обязательно. Можно использовать и существующие переменные. Однако при деструктуризации объекта может потребоваться обернуть выражение в скобки.

  • Вложенные объекты и массивы тоже работают, при деструктуризации нужно лишь сохранить ту же структуру, что и исходный объект/массив.

Как мы увидим далее, деструктуризации особенно удобны при чтении объектных параметров функций.

Карта учебника

Комментарии

перед тем как писать…
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.