[Перевод] Введение в стрелочные функции (arrow functions) в JavaScript ES6
“Толстые” стрелочные функции (=>), так же известные, как arrow функции – абсолютно новая функциональность в ECMAScript 2015 (ранее известном под именем ES6). Если верить слухам, то в ECMAScript 2015 => синтаксис стал использоваться вместо –> синтаксиса под влиянием CoffeeScript. Так же, не последнюю роль сыграла похожесть передачи контекста this.
У стрелочных функций есть две главные задачи: обеспечить более лаконичный синтаксис; обеспечить передачу лексического this с родительским scope. Давайте детально рассмотрим каждую из них!
Новый синтаксис функций
Классический синтаксис функций в JavaScript отличается ригидностью, будь это функция с одной переменной или страница с множеством функций. Каждый раз, когда вам надо вызвать функцию, вам необходимо прописать function () {}. Потребность в более лаконичном синтаксисе функций была одной из причин, почему в свое время CoffeeScript стал очень популярен. Эта потребность особенно очевидна в случае с небольшими callback функциями. Давайте просто взглянем на цепочку Promise:
function getVerifiedToken(selector) {
return getUsers(selector)
.then(function (users) { return users[0]; })
.then(verifyUser)
.then(function (user, verifiedToken) { return verifiedToken; })
.catch(function (err) { log(err.stack); });
}
Вверху вы видите более или менее удобоваримый код, написанный с использованием классического синтаксиса function в JavaScript. А вот так выглядит тот же самый код, переписанный с использованием стрелочного синтаксиса:
function getVerifiedToken(selector) {
return getUsers(selector)
.then(users => users[0])
.then(verifyUser)
.then((user, verifiedToken) => verifiedToken)
.catch(err => log(err.stack));
}
Здесь надо обратить внимание на несколько важных моментов:
- Мы потеряли function и {}, потому что наши callback функции записываются в одну строку.
- Мы убрали (). Теперь они не обертывают список аргументов, когда присутствует только один аргумент (остальные аргументы проходят как исключения; например, (...args) => ...).
- Мы избавились от ключевого слова return. Убирая {}, мы позволяем однострочным стрелочным функциям провести имплицитный возврат (в других языках такие функции часто называют лямбда функциями).
Еще раз обратим внимание на последний пункт. Имплицитный возврат происходит только в случае с однострочными стрелочными функциями. Когда стрелочная функция определяется с {}, даже если она является отдельным оператором, имплицитный возврат не происходит.
const getVerifiedToken = selector => {
return getUsers()
.then(users => users[0])
.then(verifyUser)
.then((user, verifiedToken) => verifiedToken)
.catch(err => log(err.stack));
}
Здесь начинается самое интересное. Так как у нашей функции есть только один оператор, мы можем убрать {}, и код будет очень похож на синтаксис CoffeeScript:
const getVerifiedToken = selector =>
getUsers()
.then(users => users[0])
.then(verifyUser)
.then((user, verifiedToken) => verifiedToken)
.catch(err => log(err.stack));
И все же код выше написан с использованием синтаксиса ES2015. (Я тоже удивился, что он прекрасно скомпилировался.) Когда мы говорим о стрелочных функциях с одним оператором, это не значит, что оператор не может занимать больше одной строки, для удобства использования.
Есть, однако, один существенный минус: убрав {} из стрелочных функций, как мы можем возвратить пустой объект? Например, тот же {}?
const emptyObject = () => {};
emptyObject(); // ?
А вот как выглядит весь код вместе:
function () { return 1; }
() => { return 1; }
() => 1
function (a) { return a * 2; }
(a) => { return a * 2; }
(a) => a * 2
a => a * 2
function (a, b) { return a * b; }
(a, b) => { return a * b; }
(a, b) => a * b
function () { return arguments[0]; }
(...args) => args[0]
() => {} // undefined
() => ({}) // {}
Лексический this
История о том, как this пытались протащить в JavaScript, уже покрылась пылью. Каждая function в JavaScript задает свой собственный контекст для this. Этот контекст, с одной стороны, очень легко обойти, а, с другой стороны, он крайне раздражает. На примере ниже вы видите код для часов, которые обновляют данные каждую секунду, обращаясь к jQuery:
$('.current-time').each(function () {
setInterval(function () {
$(this).text(Date.now());
}, 1000);
});
При попытке сослаться на this DOM элемента, заданный через each в callback’е setInterval, мы, к сожалению, получаем совсем другой this, – тот, который принадлежит callback. Обойти этот момент можно, задав переменную that или self:
$('.current-time').each(function () {
var self = this;
setInterval(function () {
$(self).text(Date.now());
}, 1000);
});
“Толстые” стрелочные функции могут помочь решить эту проблему, так как они не имеют this:
$('.current-time').each(function () {
setInterval(() => $(this).text(Date.now()), 1000);
});
Как насчет аргументов?
Одним из минусов стрелочных функций является то, что у них нет собственной переменной arguments, как у обычных функций:
function log(msg) {
const print = () => console.log(arguments[0]);
print(`LOG: ${msg}`);
}
log('hello'); // hello
Повторимся, что у стрелочных функций нет this и нет arguments. Однако, приняв это во внимание, вы все же можете получить аргументы, переданные в стрелочные функции с помощью rest-параметров (так же известны, как spread операторы):
function log(msg) {
const print = (...args) => console.log(args[0]);
print(`LOG: ${msg}`);
}
log('hello'); // LOG: hello
Как насчет генераторов?
“Толстые” стрелочные функции не могут использоваться как генераторы. Никаких исключений и обходных путей нет. Точка.Вывод
“Толстые” стрелочные функции – одна из причин, почему я так люблю JavaScript. Очень соблазнительно просто начать использовать => вместо function. Я видел целые библиотеки, где используется только вариант =>. Не думаю, однако, что это разумно. В конце концов, у => есть много особенностей и скрытых функций. Я рекомендую использовать стрелочные функции только там, где вам нужна новая функциональность:
- Функции с одиночными операторами, которые сразу же делают возврат;
- функции, которые должны работать с this с родительским scope.
ES6 сегодня
Так можно ли воспользоваться возможностями ES6 уже сегодня? Использование транспайлеров стало нормой в последние несколько лет. Ни простые разработчики, ни крупные компании не стесняются их применять. Babel — транспайлер из ES6 в ES5, который поддерживает все нововведения ES6.
Если используете Browserify, то добавить Babel вы сможете всего за пару минут. Конечно же, есть поддержка практически для любого билда с Node.js. Например: Gulp, Grunt и многие другие.
Как насчет браузеров?
Большинство браузеров постепенно добавляют новые функции, но полной поддержки пока нет ни у кого. Что, теперь ждать? Зависит. Имеет смысл начать использовать функции языка, которые станут универсальными через год-два. Это позволит вам комфортно перейти на новый этап. Однако, если вам нужен 100% контроль над исходным кодом, то пока лучше подождать и использовать ES5.
Перевод подготовили: greebn9k(Сергей Грибняк), silmarilion(Андрей Хахарев)