CSS-live.ru

ES6: стрелочные функции изнутри

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

Ежедневная эпопея статей «ES6 изнутри» продолжается. Сегодня мы обсудим стрелочные функции. В прошлых статьях мы познакомились с деструктированием и литералами шаблона. Я стараюсь разобрать всё, что относится к набору фич ES6, и наконец добраться до ES7. Также я заметил, что, когда я пишу про эти фичи, они навсегда врезаются мне в память.

Раз вы читаете эти статьи, предлагаю установить Babel и babel-node, и повторять за мной, копируя отдельные примеры в файл. Можете затем запустить их при помощи babel-node yourfile в терминале. Самостоятельный запуск этих примеров, и, возможно, небольшая их корректировочка помогут лучше усвоить эти новые фичи — даже если вы просто добавите оператор console.log, чтобы разобраться в происходящем.

Итак, встречайте наших сегодняшних гостей.

В прошлых статьях мы уже затрагивали стрелочные функции вскользь, применяя их мимоходом и не рассуждая о происходящем. Эту статью мы посвятим преимущественно стрелочным функциям, а остальной ES6 отложим до лучших времен. Я считаю, что это лучший способ писать о ES6 — выводить на первый план одну фичу в каждой статье, постепенно добавляя другие и объединяя разные идеи, чтобы понимать, как они все взаимодействуют. Я часто наблюдал, как несколько фич ES6 вместе образуют суперфичу, и это потрясающе. И всё же важно погружаться в синтаксис и фичи ES6 постепенно, а не бросаться в воду, потому что чем дальше, тем она горячее, и к этому лучше привыкать постепенно — вероятно это была плохая аналогия, но двигаемся дальше.

Применение стрелочных функций в JavaScript

Стрелочные функции доступны во многих современных языках, и как же мне их недоставало несколько лет назад, когда я переходил с C# на JavaScript. К счастью, теперь они появились в ES6, а значит доступны нам в JavaScript. Их синтаксис довольно выразителен. У нас уже есть анонимные функции, но порой приятно иметь краткую альтернативу.

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

[1, 2, 3].map(num => num * 2)
// <- [2, 4, 6]

А вот как это выглядело бы в ES5:

[1, 2, 3].map(function (num) { return num * 2 })
// <- [2, 4, 6]

Если нужно объявить больше аргументов (или аргументов нет), то необходимы круглые скобки.

[1, 2, 3, 4].map((num, index) => num * 2 + index)
// <- [2, 5, 8, 11]

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

[1, 2, 3, 4].map(num => {
  var multiplier = 2 + num
  return num * multiplier
})
// <- [3, 8, 15, 24]

Также с помощью синтаксиса круглых скобок можно добавить больше аргументов.

[1, 2, 3, 4].map((num, index) => {
  var multiplier = 2 + index
  return num * multiplier
})
// <- [2, 6, 12, 20]

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

  • (num, index) =>лишь чуть-чуть короче function (num, index)
  • Запись function позволяет именовать метод, улучшая качество кода.
  • Когда у функции есть множество аргументов и операторов, то, на мой взгляд, шесть дополнительных символов не сыграют особой роли.
  • Однако, с именованным методом код станет настолько понятнее, что те шесть лишних символов (плюс имя метода) сполна себя оправдают

Далее, если нам нужно вернуть литерал объекта, то придётся обернуть выражение в круглые скобки. Тем самым литерал объекта не будет интерпретироваться, как блок оператора (что привело бы к незаметной ошибке, или того хуже, к ошибке синтаксиса, поскольку number: n — недопустимое выражение в примере ниже. В первом примере код интерпретируется как метка number, после которой идет выражение n. Поскольку мы находимся в блоке и ничего не возвращаем, на выходе каждый элемент массива получит значение undefined. Во втором случае, после метки и выражения n, , something: 'else' не имеет смысла для компилятора, и выбрасывается SyntaxError.)

[1, 2, 3].map(n => { number: n })
// [undefined, undefined, undefined]
[1, 2, 3].map(n => { number: n, something: 'else' })
// <- SyntaxError
[1, 2, 3].map(n => ({ number: n }))
// <- [{ number: 1 }, { number: 2 }, { number: 3 }]
[1, 2, 3].map(n => ({ number: n, something: 'else' }))
/* <- [
  { number: 1, something: 'else' },
  { number: 2, something: 'else' },
  { number: 3, something: 'else' }]
*/

Огромный плюс стрелочных функций в ES6 — они связаны со своей лексической областью. Это значит, что можно попрощаться с var self = this и похожими хаками, — например .bind(this) — чтобы сохранить контекст в пределах глубоко вложенных методов.

function Timer () {
  this.seconds = 0
  setInterval(() => this.seconds++, 1000)
}
var timer = new Timer()
setTimeout(() => console.log(timer.seconds), 3100)
// <- 3

Помните, что связывание лексического this в стрелочных функциях ES6 означает, что .call и .apply не смогут изменить контекст. Однако, это скорее фича, а не баг.

Заключение

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

Не стоит слепо менять все объявления функций на стрелочные, если только их аргументы и тело выражения не говорят сами за себя. Я большой сторонник именованных объявлений функций, поскольку они улучшают читаемость кода, не требуя комментариев — а значит, в большинстве случаев я лишь усложню себе жизнь, перейдя на стрелочные функции.

С другой стороны, я считаю, что стрелочные функции особенно полезны в большинстве задач функционального программирования, например, при использовании .map, .filter или .reduce с коллекциями. Кроме того, по-настоящему полезными стрелочные функции окажутся в асинхронных скриптах, в которых, как правило, бесчисленные обратные вызовы только и делают, что передают друг другу аргумент — вот где стрелочные функции предстают во всей красе.

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

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

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

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