CSS-live.ru

Деструктурирование в JavaScript (ES6) изнутри

Перевод статьи ES6 JavaScript Destructuring in Depth  с сайта ponyfoo.com, опубликовано на css-live.ru с разрешения автора — Николаса Беваквы.

В моей серии статей про React я кратко упомянул несколько фич ES6 (и введение в Babel), а теперь хочу сосредоточиться на самих фичах языка. Я уже много всего читал про ES6 и ES7, и пора уже поговорить об их фичах здесь на Pony Foo.

Эта статья предупреждает об опасности переборщить с фичами языка ES6. Затем начнём серию с обсуждения деструктирования в ES6 и когда оно наиболее полезно, а также некоторые из его подводных камней и нюансов.

Предостережение

Если вы не уверены, то скорее всего вам стоит остановиться на ES5 и старом синтаксисе, а не переходить на ES6 ради самого ES6. И здесь я не утверждаю, что синтаксис ES6 — плохая идея, совсем наоборот, видите, я пишу статью про ES6! Я беспокоюсь о том, что пользоваться фичами ES6 нужно потому, что они безусловно улучшают качество кода, а не потому, что «это модно» — как бы оно ни было.

Подход, который я использую до сих пор — писать на простом ES5, добавляя по необходимости «сахарок» ES6 там, где он действительно улучшает мой код. Полагаю, что со временем я смогу намного быстрее выявлять сценарии, где вместо ES5 стоит использовать фичи ES6, но поначалу лучше не переусердствовать раньше времени. Вместо этого тщательно анализируйте, что в первую очередь подошло бы вашему коду, и применяйте ES6 с умом.

Таким образом, вы научитесь применять новые фичи с пользой, а не просто выучите синтаксис.

Деструктирование

Это одна из тех фич, что я использую чаще всего. И одна из простых фич. Она связывает свойства с любым количеством переменных и работает как с массивами Array, так и с объектами Object.

var foo = { bar: 'pony', baz: 3 }
var {bar, baz} = foo
console.log(bar)
// <- 'pony'
console.log(baz)
// <- 3

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

var foo = { bar: 'pony', baz: 3 }
var {bar: a, baz: b} = foo
console.log(a)
// <- 'pony'
console.log(b)
// <- 3

Можно вытаскивать свойства с любой глубины вложенности, и для этих глубоких связей тоже можно задавать псевдонимы.

var foo = { bar: { deep: 'pony', dangerouslySetInnerHTML: 'lol' } }
var {bar: { deep, dangerouslySetInnerHTML: sure }} = foo
console.log(deep)
// <- 'pony'
console.log(sure)
// <- 'lol'

По умолчанию ненайденные свойства будут undefined, как и при доступе к свойствам объекта при помощи нотаций точки или скобок.

var {foo} = {bar: 'baz'}
console.log(foo)
// <- undefined

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

var {foo:{bar}} = {baz: 'ouch'}
// <- Исключение

Это вполне разумно, если думать о деструктировании, как о «сахарке» для  ES5, как в коде ниже.

var _temp = { baz: 'ouch' }
var bar = _temp.foo.bar
// <- Исключение

Классная особенность деструктирования — оно позволяет обменивать значения переменных без пресловутой вспомогательной переменной.

function es5 () {
  var left = 10
  var right = 20
  var aux
  if (right > left) {
    aux = right
    right = left
    left = aux
  }
}
function es6 () {
  var left = 10
  var right = 20
  if (right > left) {
    [left, right] = [right, left]
  }
}

Ещё один удобный аспект деструктирования — возможность вытаскивать ключи с помощью динамических имен свойства.

var key = 'such_dynamic'
var { [key]: foo } = { such_dynamic: 'bar' }
console.log(foo)
// <- 'bar'

В ES5 вам бы для этого понадобилось добавить целый оператор и объявить лишнюю переменную

var key = 'such_dynamic'
var baz = { such_dynamic: 'bar' }
var foo = baz[key]
console.log(foo)

Также можно задать значение по умолчанию, на случай, если полученное свойство окажется  со значением undefined.

var {foo=3} = { foo: 2 }
console.log(foo)
// <- 2
var {foo=3} = { foo: undefined }
console.log(foo)
// <- 3
var {foo=3} = { bar: 2 }
console.log(foo)
// <- 3

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

var [a] = [10]
console.log(a)
// <- 10

Здесь, опять же, можно использовать значение по умолчанию, следуя тем же правилам.

var [a] = []
console.log(a)
// <- undefined
var [b=10] = [undefined]
console.log(b)
// <- 10
var [c=10] = []
console.log(c)
// <- 10

Когда дело доходит до массивов, то можно смело пропускать ненужные элементы.

var [,,a,b] = [1,2,3,4,5]
console.log(a)
// <- 3
console.log(b)
// <- 4

Также деструктирование можно использовать в списке параметров function.

function greet ({ age, name:greeting='she' }) {
  console.log(`${greeting} is ${age} years old.`)
}
greet({ name: 'nico', age: 27 })
// <- 'nico is 27 years old'
greet({ age: 24 })
// <- 'she is 24 years old'

Это было в общих чертах о том, как использовать деструктурирование. Но для чего оно нужно?

Применение деструктирования

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

function getCoords () {
  return {
    x: 10,
    y: 22
  }
}
var {x, y} = getCoords()
console.log(x)
// <- 10
console.log(y)
// <- 22

Аналогичный случай, но в обратную сторону — возможность определить параметр по умолчанию, когда есть метод с кучей параметров, требующих значения по умолчанию. Это особенно интересно в качестве замены для именованных параметров, как в других языках, напр. Python и C#.

function random ({ min=1, max=300 }) {
  return Math.floor(Math.random() * (max - min)) + min
}
console.log(random({}))
// <- 174
console.log(random({max: 24}))
// <- 18

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

function random ({ min=1, max=300 } = {}) {
  return Math.floor(Math.random() * (max - min)) + min
}
console.log(random())
// <- 133

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

function getUrlParts (url) {
  var magic = /^(https?):\/\/(ponyfoo\.com)(\/articles\/([a-z0-9-]+))$/
  return magic.exec(url)
}
var parts = getUrlParts('http://ponyfoo.com/articles/es6-destructuring-in-depth')
var [,protocol,host,pathname,slug] = parts
console.log(protocol)
// <- 'http'
console.log(host)
// <- 'ponyfoo.com'
console.log(pathname)
// <- '/articles/es6-destructuring-in-depth'
console.log(slug)
// <- 'es6-destructuring-in-depth'

Особый случай: операторы import

Хотя операторы import не следуют правилам деструктирования, они ведут себя в чем-то похоже. Вот, пожалуй, похожий на деструктурирование пример, который мне доводится использовать чаще всего, хоть это и не деструктурирование как таковое. Всякий раз, когда вы пишите операторы import для модуля, можно вытащить нужное из публичного API модуля. Пример с использованием contra:

import {series, concurrent, map } from 'contra'
series(tasks, done)
concurrent(tasks, done)
map(items, mapper, done)

Однако, заметьте, что у операторов import другой синтаксис. По сравнению с деструктированием, ни один из следующих операторов import не работает.

  • Применение значений по умолчанию, как в import {series = noop} from 'contra'
  • Аналог «глубокого» деструктирования, например import {map: { series }} from 'contra'
  • Синтаксис с псевдонимом import {map: mapAsync} from 'contra'

Основная причина этих ограничений — оператор import вносит привязку, а не ссылку или значение. Это важное различие, которое мы изучим подробнее в будущей статьи про модули ES6.

Я ежедневно буду знакомить вас с фичами ес6 и ес7, так что убедитесь, поэтому не забудьте подписаться, чтобы узнать больше!

P.S. Это тоже может быть интересно:

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

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