У стрелочных функций есть две главные задачи: обеспечить более лаконичный синтаксис; обеспечить передачу лексического 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(Андрей Хахарев)
Комментарии (45)
ghosthope
14.10.2015 14:05Мне одному режет глаз фраза «Каждый раз, когда вам надо вызвать функцию, вам необходимо прописать function () {}.»?
NeXTs_od
14.10.2015 16:48+4Узнал для себя что
1) С {} неявного возврата не будет
2) Чтоб вернуть пустой объект, оборачиваем в ({})
3) У стрелочных функций нет arguments, а достать их можно через ...args
спасибо
dom1n1k
14.10.2015 17:57-1Мелкие функции-однострочники приветствую, а вот за использование => в больших многострочных функциях отбивал бы руки.
hell0w0rd
14.10.2015 18:12+1И что тут не так?
passport.deserializeUser((id, done) => { User.findOne({ where: {id} }).then((user) => { done(null, user); }).catch(done); });
dom1n1k
14.10.2015 18:44Слово function хорошо бросается в глаза и сразу дает понять, что это самостоятельный логический кирпич кода.
А тут — фрагмент, как будто вырванный из контекста. Стрелка видна плохо, всё сливается. То ли функция, то ли длинный цепной вызов.
Для мелких коллбеков это плюс, для самостоятельных функций — минус.hell0w0rd
14.10.2015 18:54+3C подсветкой все нормально. Ни разу не было проблем, почти год уже с ES6.
dom1n1k
14.10.2015 18:56-1Не нормально. И именно поэтому я не перевариваю кофескрипт, там всё ещё в разы хуже (хотя и признаю, что в нем есть некоторые интересные фишки).
Eternalko
15.10.2015 02:20Сравнения ради примерно так выглядит похожий код в CS
ST3
dom1n1k
15.10.2015 03:29-1Почти полное отсутствие скобок (как круглых, так и фигурных), давая смешную экономию на количестве символов, убивает читаемость на корню. Я вынужден всматриваться в строку, чтобы для начала понять, а где тут вообще что? Работать парсером, фактически. Где ключевое слово, где имя функции, где параметр, где переменные? И даже подсветка не спасает.
Eternalko
15.10.2015 04:40С точки зрения людей пишущих на CS отсутствие лишних символов и пустых слов (типа function) увеличивает читаемость.
faiwer
15.10.2015 08:22Отсутствие фигурных скобок, на мой взгляд, тут только увеличивает читаемость. Как и в ruby и в python-е. Возможно просто дело привычки. А вот слабость подсветки на лицо. Действительно бедная палитра. На моих скриншотах снизу больше «светофора».
faiwer
14.10.2015 20:08Ну это уже от подсветки зависит. У меня в st3 "=>" выделяется добротно и глаза за него цепляются не хуже, чем за function. А вот раздражает он куда меньше.
скриншотdom1n1k
15.10.2015 03:36В данном случае внутри map находится, фактически, однострочник, который зачем-то написали в 5 строк. Можно вполне обойтись без промежуточной переменной name, воткнув вызов getName инлайн. А ещё лучше заиметь функцию getValueById.
А я говорил про действительно полноценные функции, составляющие некий логический блок.faiwer
15.10.2015 08:20Я Код от балды написал. Зря вы к нему цепляетесь :) Ключевой момент, что без египетских кавычек и хорошей цветовой схеме, всё более чем наглядно.
Ещё скриншот
aparamonov
15.10.2015 05:52Это и правда кошмарно читается — надо всматриваться где начало и конец блоков/вызовов, слишком много вложенных отступов/блоков.
Пример чуть побольше — и разработчики с ума сойдут.
const findUser = (id) => User.findOne({where: {id}}) const onSuccess = (user) => done(null, user) passport .deserializeUser(findUser) .then(onSuccess) .catch(done);
Mithgol
15.10.2015 08:23Вокруг единственного аргумента можно не ставить скобки:
const findUser = id => User.findOne({where: {id}}) const onSuccess = user => done(null, user)
hell0w0rd
15.10.2015 08:55+1По кодстайлу, на пример, Airbnb, так делать запрещено. На мой взгляд вот как раз эта возможность и ее использование может ухудшить читаемость
everyonesdesign
15.10.2015 12:05+2Неправда, у них сказано как раз наоборот:
If your function only takes a single argument, feel free to omit the parentheses.
hell0w0rd
15.10.2015 13:12+1Хм. Время идет, люди и гады меняются. Изначально было не так: github.com/airbnb/javascript/blob/58cdc731f435db78f3c1f4d466284796128fb75b/README.md#arrow-functions
Я все еще придерживаюсь старого гайда. Они еще перешли на trailing comma в описание объекта, что мне вообще не нравится.trikadin
15.10.2015 19:45Наверное, люди и гайды?)
От себя замечу, что считаю более разумным всегда ставить круглые скобки вокруг аргументов — во-первых, это увеличивает консистентность кода, во-вторых, количество аргументов может увеличиться.hell0w0rd
15.10.2015 21:04Забавная очепятка.
Да, полностью согласен. Можно такого написать, что пару минут вчитываться прийдется.
const foo = a => b => c => a + b + c;
trikadin
15.10.2015 21:13Да не, такого-то ни один разумный (во всяком случае, слышавший о правиле маньяка) программист писать не будет. А вот не ставить скобки соблазн может быть огромный. Но всё же считаю это плохой практикой.
Хотя я тоже не без греха. Например, у меня в коде иногда бывают такие штуки:
const values = $C(obj).reduce((result, value) => (result.push(value), result), []);
$C, если что — это от библиотечки, которая предоставляет одинаковый набор методов для любых коллекций.
Mithgol
15.10.2015 08:20И что тут не так?
Вместо «(user)» можно просто «user» было бы записать.
(Не ошибка, но скобки лишние.)
hell0w0rd
14.10.2015 18:14+4У arrow-function не просто нет this, у них нет прототипа.
Arilas
15.10.2015 00:44При этом, даже создатели V8 заявляли, Arrow Functions инициализируются быстрее, вызов быстрее так как:
1. Не создается прототип
2. Не создается arguments
3. Не нужно регистрировать ее как функцию конструктор (нельзя через new писать)
SamVimes
15.10.2015 20:08Возможно глупый вопрос, но почему лямбды называют стрелочными функциями?
faiwer
15.10.2015 20:14+2Скажите, а что такое лямбда? Описание на той же вики, не очень проясняет вопрос. Лямбда обязана быть однострочной? Если да, то стрелочные функции — не обязаны. Лямбда это просто анонимный метод, определённый прямо посреди кода? Если так, то и обычные function-ы, без явно заданного имени, получается, тоже лямбды?
SamVimes
16.10.2015 16:42+1Ну, вопрос хороший, а я, к сожалению, не эксперт, но мне казалось, что в лямбде отсутствует контекст, т.е. (вроде как) используется контекст того «места», где лямбда создана. Опять таки боюсь ошибиться, но у function контекст есть.
kvas
20.10.2015 12:28Обычно лямбда или лямбда функция — это программистский слэнг, означающий «анонимная функция». Изначально это пошло из лямбда-исчисления, где вычисления представляются в виде лямбда-форм, которые по сути дела являются анонимными функциями. Во многих языках программирования ключевое слово lambda используется для определения анонимных функций, поэтому их стали называть лямбдами, иногда даже в языках где такого ключевого слова нет.
Стрелочные функции — это анонимные функции, так что да, их можно называть лямбдами. Но в JavaScript'е есть ещё обычные анонимные функции, которые тоже в общем-то лямбды, несмотря на их развесистый синтаксис. Возможно поэтому тут используется более специфичный термин «стрелочные функции».
RusSuckOFF
Стрелочные функции (Arrow functions) в ECMAScript 6 — и название такое же, и тема раскрыта полнее.
greebn9k
Спасибо за полезную ссылку. Пригодится тем, кто ознакомится с этой статьей.