Введение в стрелочные функции (arrow functions) в JavaScript ES6

https://strongloop.com/strongblog/an-introduction-to-javascript-es6-arrow-functions/
  • Перевод
“Толстые” стрелочные функции (=>), так же известные, как 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(Андрей Хахарев)
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 45
  • +5
    Стрелочные функции (Arrow functions) в ECMAScript 6 — и название такое же, и тема раскрыта полнее.
    • +1
      Спасибо за полезную ссылку. Пригодится тем, кто ознакомится с этой статьей.
    • 0
      Вы бы поделились с silmarilion'ом и дали ему хоть один совместный перевод от своего имени опубликовать. Хотел Вашего соавтора плюсануть на Мегамозге, а у него там нет ни одной публикации. Здесь он вообще ридонли.
    • 0
      Мне одному режет глаз фраза «Каждый раз, когда вам надо вызвать функцию, вам необходимо прописать function () {}.»?
      • 0
        Поправили.
        • +2
          «Зависит.» Это же не английский, незачем использовать прямую кальку с фразы «It depends».
          • –3
            А чем плохо слово «зависит», даже если это и калька? По-моему, лаконично и все объясняет.
      • +1
        Имплицитный возврат — насколько часто встречающийся термин? Он действительно лучше, чем тот же «неявный»?
        • +1
          Похоже, что мы стали новаторами. Поправили на «неявный».
        • +8
          Ещё одна статья про arrow functions в ES6? Надо прочитать, тема сложная.
          • +4
            Узнал для себя что
            1) С {} неявного возврата не будет
            2) Чтоб вернуть пустой объект, оборачиваем в ({})
            3) У стрелочных функций нет arguments, а достать их можно через ...args
            спасибо
            • –1
              Мелкие функции-однострочники приветствую, а вот за использование => в больших многострочных функциях отбивал бы руки.
              • +1
                И что тут не так?
                passport.deserializeUser((id, done) => {
                  User.findOne({
                    where: {id}
                  }).then((user) => {
                    done(null, user);
                  }).catch(done);
                });
                
                • 0
                  Слово function хорошо бросается в глаза и сразу дает понять, что это самостоятельный логический кирпич кода.
                  А тут — фрагмент, как будто вырванный из контекста. Стрелка видна плохо, всё сливается. То ли функция, то ли длинный цепной вызов.
                  Для мелких коллбеков это плюс, для самостоятельных функций — минус.
                  • +3
                    C подсветкой все нормально. Ни разу не было проблем, почти год уже с ES6.

                    • –1
                      Не нормально. И именно поэтому я не перевариваю кофескрипт, там всё ещё в разы хуже (хотя и признаю, что в нем есть некоторые интересные фишки).
                      • +4
                        Нормально
                        • 0
                          Сравнения ради примерно так выглядит похожий код в CS
                          ST3


                          • –1
                            Почти полное отсутствие скобок (как круглых, так и фигурных), давая смешную экономию на количестве символов, убивает читаемость на корню. Я вынужден всматриваться в строку, чтобы для начала понять, а где тут вообще что? Работать парсером, фактически. Где ключевое слово, где имя функции, где параметр, где переменные? И даже подсветка не спасает.
                            • 0
                              С точки зрения людей пишущих на CS отсутствие лишних символов и пустых слов (типа function) увеличивает читаемость.
                              • +2
                                Первый раз вижу CS. Отлично читается.
                                • 0
                                  Отсутствие фигурных скобок, на мой взгляд, тут только увеличивает читаемость. Как и в ruby и в python-е. Возможно просто дело привычки. А вот слабость подсветки на лицо. Действительно бедная палитра. На моих скриншотах снизу больше «светофора».
                                  • 0
                                    У меня она специально выключена.
                          • 0
                            Ну это уже от подсветки зависит. У меня в st3 "=>" выделяется добротно и глаза за него цепляются не хуже, чем за function. А вот раздражает он куда меньше.
                            скриншот
                            image

                            В общем тут скорее на вкус и цвет. Отбивать руки явно не стоит.
                            • 0
                              В данном случае внутри map находится, фактически, однострочник, который зачем-то написали в 5 строк. Можно вполне обойтись без промежуточной переменной name, воткнув вызов getName инлайн. А ещё лучше заиметь функцию getValueById.
                              А я говорил про действительно полноценные функции, составляющие некий логический блок.
                              • 0
                                Я Код от балды написал. Зря вы к нему цепляетесь :) Ключевой момент, что без египетских кавычек и хорошей цветовой схеме, всё более чем наглядно.

                                Ещё скриншот
                                image
                          • 0
                            Это и правда кошмарно читается — надо всматриваться где начало и конец блоков/вызовов, слишком много вложенных отступов/блоков.
                            Пример чуть побольше — и разработчики с ума сойдут.

                            const findUser = (id) => User.findOne({where: {id}})
                            const onSuccess = (user) => done(null, user)
                            
                            passport
                              .deserializeUser(findUser)
                              .then(onSuccess)
                              .catch(done);
                            
                            • 0
                              Вокруг единственного аргумента можно не ставить скобки:
                              const findUser = id => User.findOne({where: {id}})
                              const onSuccess = user => done(null, user)
                              
                              • +1
                                По кодстайлу, на пример, Airbnb, так делать запрещено. На мой взгляд вот как раз эта возможность и ее использование может ухудшить читаемость
                                • +2
                                  Неправда, у них сказано как раз наоборот:
                                  If your function only takes a single argument, feel free to omit the parentheses.
                                  • +1
                                    Хм. Время идет, люди и гады меняются. Изначально было не так: github.com/airbnb/javascript/blob/58cdc731f435db78f3c1f4d466284796128fb75b/README.md#arrow-functions
                                    Я все еще придерживаюсь старого гайда. Они еще перешли на trailing comma в описание объекта, что мне вообще не нравится.
                                    • 0
                                      Наверное, люди и гайды?)

                                      От себя замечу, что считаю более разумным всегда ставить круглые скобки вокруг аргументов — во-первых, это увеличивает консистентность кода, во-вторых, количество аргументов может увеличиться.
                                      • 0
                                        Забавная очепятка.
                                        Да, полностью согласен. Можно такого написать, что пару минут вчитываться прийдется.
                                        const foo = a => b => c => a + b + c;
                                        
                                        • 0
                                          Да не, такого-то ни один разумный (во всяком случае, слышавший о правиле маньяка) программист писать не будет. А вот не ставить скобки соблазн может быть огромный. Но всё же считаю это плохой практикой.

                                          Хотя я тоже не без греха. Например, у меня в коде иногда бывают такие штуки:
                                          const values = $C(obj).reduce((result, value) => (result.push(value), result), []);
                                          


                                          $C, если что — это от библиотечки, которая предоставляет одинаковый набор методов для любых коллекций.
                                          • +1
                                            Типичное каррирование, ИМХО оно всегда нечитабельное
                              • 0
                                И что тут не так?
                                Вместо «(user)» можно просто «user» было бы записать.

                                (Не ошибка, но скобки лишние.)
                            • +4
                              У arrow-function не просто нет this, у них нет прототипа.
                              • 0
                                При этом, даже создатели V8 заявляли, Arrow Functions инициализируются быстрее, вызов быстрее так как:
                                1. Не создается прототип
                                2. Не создается arguments
                                3. Не нужно регистрировать ее как функцию конструктор (нельзя через new писать)
                                • 0
                                  Ну это понятно. Просто все эти особенности хорошо бы учитывать.
                              • 0
                                Возможно глупый вопрос, но почему лямбды называют стрелочными функциями?
                                • +2
                                  Скажите, а что такое лямбда? Описание на той же вики, не очень проясняет вопрос. Лямбда обязана быть однострочной? Если да, то стрелочные функции — не обязаны. Лямбда это просто анонимный метод, определённый прямо посреди кода? Если так, то и обычные function-ы, без явно заданного имени, получается, тоже лямбды?
                                  • +1
                                    Ну, вопрос хороший, а я, к сожалению, не эксперт, но мне казалось, что в лямбде отсутствует контекст, т.е. (вроде как) используется контекст того «места», где лямбда создана. Опять таки боюсь ошибиться, но у function контекст есть.
                                    • +1
                                      Ну по такому критерию получается что да, это лямбды.
                                    • +2
                                      Обычно лямбда или лямбда функция — это программистский слэнг, означающий «анонимная функция». Изначально это пошло из лямбда-исчисления, где вычисления представляются в виде лямбда-форм, которые по сути дела являются анонимными функциями. Во многих языках программирования ключевое слово lambda используется для определения анонимных функций, поэтому их стали называть лямбдами, иногда даже в языках где такого ключевого слова нет.

                                      Стрелочные функции — это анонимные функции, так что да, их можно называть лямбдами. Но в JavaScript'е есть ещё обычные анонимные функции, которые тоже в общем-то лямбды, несмотря на их развесистый синтаксис. Возможно поэтому тут используется более специфичный термин «стрелочные функции».

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

                                  Интересные публикации