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. Это тоже может быть интересно:
None Found