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

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

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


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

const pair = ['express', 399]
const [type, fee] = pair
// type === 'express', fee === 399

Пропуск элементов:

const rgb = [255, 120, 64]
const [r, , b] = rgb
// r === 255, b === 64

Обмен без временной переменной:

let a = 1
let b = 2
;[a, b] = [b, a]
// a === 2, b === 1

Значения по умолчанию (массив) #

const coords = [10]
const [x = 0, y = 0] = coords
// x === 10, y === 0

Если элемент undefined, подставится значение по умолчанию; если элемент отсутствует в разреженном массиве — тоже.


Rest в массиве #

const queue = ['priority', 'order-1', 'order-2', 'order-3']
const [head, ...rest] = queue
// head === 'priority', rest === ['order-1', 'order-2', 'order-3']

rest должен быть последним в списке слева.


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

const line = { sku: 'kb-01', price: 4900, qty: 2 }

const { sku, qty } = line
// sku === 'kb-01', qty === 2

Имя переменной совпадает с ключом. Другое имя — через псевдоним:

const { sku: productCode, price: unitPrice } = line
// productCode === 'kb-01', unitPrice === 4900

Значения по умолчанию (объект) #

const delivery = { zone: 'center' }

const { zone, fee = 199, express = false } = delivery
// zone === 'center', fee === 199, express === false

По умолчанию срабатывает при undefined, не при null:

const { note = 'без комментария' } = { note: null }
// note === null

Rest в объекте #

const checkout = {
  orderId: 'ord-42',
  total: 5199,
  currency: 'RUB',
  paid: true,
}

const { orderId, ...meta } = checkout
// orderId === 'ord-42'
// meta === { total: 5199, currency: 'RUB', paid: true }

Удобно отделить идентификатор от остального payload при логировании.


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

const response = {
  data: {
    cart: {
      lines: [{ sku: 'ms-02', qty: 1 }],
    },
  },
}

const {
  data: {
    cart: { lines },
  },
} = response

lines[0].sku // "ms-02"

Глубокие цепочки ухудшают читаемость — на 2–3 уровня обычно достаточно; дальше вынесите промежуточную переменную.


Деструктуризация в параметрах функции #

function formatLine({ sku, qty, price = 0 }) {
  return `${sku} × ${qty} = ${price * qty} ₽`
}

formatLine({ sku: 'hp-09', qty: 2, price: 890 })
// "hp-09 × 2 = 1780 ₽"

Значения по умолчанию для всего объекта:

function printEta({ minutes = 30, unit = 'мин' } = {}) {
  return `~${minutes} ${unit}`
}

printEta() // "~30 мин"
printEta({ minutes: 12 }) // "~12 мин"

= {} защищает от вызова без аргумента.


Rest в параметрах #

function sumScores(first, second, ...rest) {
  return [first, second, ...rest].reduce((s, n) => s + n, 0)
}

sumScores(10, 20, 5, 5) // 40

Связь с Функции: rest собирает «хвост» аргументов в массив.


Деструктуризация при присваивании #

let track = { id: 't1', title: 'Focus' }
;({ id, title } = track)
// скобки обязательны: без них { воспринимается как блок

В const/let скобки не нужны:

const { title } = track

Итерация entries с деструктуризацией #

const fees = new Map([
  ['standard', 199],
  ['express', 399],
])

for (const [kind, amount] of fees) {
  console.log(kind, amount)
}

Object.entries({ a: 1, b: 2 }).forEach(([key, value]) => {
  console.log(key, value)
})

Практика: разбор ответа доставки #

function normalizeDelivery(apiRow) {
  const {
    id,
    status,
    address: { city, street, flat } = {},
    eta_minutes: eta = null,
  } = apiRow

  return {
    id,
    status,
    city: city ?? '',
    street: street ?? '',
    flat: flat ?? '',
    eta,
  }
}

normalizeDelivery({
  id: 'd-7',
  status: 'en_route',
  address: { city: 'Москва', street: 'Пример, 5' },
  eta_minutes: 14,
})

Вложенный { city, street, flat } = {} не упадёт, если address отсутствует.


Практика: чекаут и плейлист #

function checkoutTotals({ lines, promo = 0, deliveryFee = 0 }) {
  const subtotal = lines.reduce((s, { price, qty }) => s + price * qty, 0)
  const total = Math.max(0, subtotal - promo + deliveryFee)
  return { subtotal, total }
}

checkoutTotals({
  lines: [
    { price: 1000, qty: 2 },
    { price: 500, qty: 1 },
  ],
  promo: 200,
  deliveryFee: 199,
})
// { subtotal: 2500, total: 2499 }
const [, second, ...others] = ['Intro', 'Main', 'Outro', 'Bonus']
// second === 'Main', others === ['Outro', 'Bonus']

Осторожно: мутация вложенных ссылок #

Деструктуризация копирует ссылки, не глубокие клоны:

const cart = [{ sku: 'a', qty: 1 }]
const [first] = cart
first.qty = 5
cart[0].qty // 5

Для иммутабельного обновления строки корзины используйте spread на уровне объекта (см. Методы массивов).


Итог #

  • Массивы: [a, b] = arr, пропуск, значения по умолчанию, ...rest.
  • Объекты: { field, other: alias } = obj, значения по умолчанию, ...rest для «всего кроме».
  • Параметры функций с деструктуризацией делают API явным; = {} защищает от undefined.
  • Вложенность удобна для JSON, но не злоупотребляйте глубиной.
  • Деструктуризация не клонирует вложенные объекты автоматически.

См. также #