“Толстые” стрелочные функции (=>), так же известные, как 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)


  1. RusSuckOFF
    14.10.2015 12:40
    +5

    Стрелочные функции (Arrow functions) в ECMAScript 6 — и название такое же, и тема раскрыта полнее.


    1. greebn9k
      14.10.2015 12:43
      +1

      Спасибо за полезную ссылку. Пригодится тем, кто ознакомится с этой статьей.


  1. valemak
    14.10.2015 12:50

    Вы бы поделились с silmarilion'ом и дали ему хоть один совместный перевод от своего имени опубликовать. Хотел Вашего соавтора плюсануть на Мегамозге, а у него там нет ни одной публикации. Здесь он вообще ридонли.


    1. greebn9k
      14.10.2015 12:56
      +1

      Учтем. Спасибо.


  1. ghosthope
    14.10.2015 14:05

    Мне одному режет глаз фраза «Каждый раз, когда вам надо вызвать функцию, вам необходимо прописать function () {}.»?


    1. greebn9k
      14.10.2015 14:45

      Поправили.


      1. Areso
        15.10.2015 06:07
        +2

        «Зависит.» Это же не английский, незачем использовать прямую кальку с фразы «It depends».


        1. greebn9k
          15.10.2015 10:31
          -3

          А чем плохо слово «зависит», даже если это и калька? По-моему, лаконично и все объясняет.


  1. gwer
    14.10.2015 14:53
    +1

    Имплицитный возврат — насколько часто встречающийся термин? Он действительно лучше, чем тот же «неявный»?


    1. greebn9k
      14.10.2015 15:11
      +1

      Похоже, что мы стали новаторами. Поправили на «неявный».


  1. Sirion
    14.10.2015 15:48
    +8

    Ещё одна статья про arrow functions в ES6? Надо прочитать, тема сложная.


  1. NeXTs_od
    14.10.2015 16:48
    +4

    Узнал для себя что
    1) С {} неявного возврата не будет
    2) Чтоб вернуть пустой объект, оборачиваем в ({})
    3) У стрелочных функций нет arguments, а достать их можно через ...args
    спасибо


  1. dom1n1k
    14.10.2015 17:57
    -1

    Мелкие функции-однострочники приветствую, а вот за использование => в больших многострочных функциях отбивал бы руки.


    1. hell0w0rd
      14.10.2015 18:12
      +1

      И что тут не так?

      passport.deserializeUser((id, done) => {
        User.findOne({
          where: {id}
        }).then((user) => {
          done(null, user);
        }).catch(done);
      });
      


      1. dom1n1k
        14.10.2015 18:44

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


        1. hell0w0rd
          14.10.2015 18:54
          +3

          C подсветкой все нормально. Ни разу не было проблем, почти год уже с ES6.


          1. dom1n1k
            14.10.2015 18:56
            -1

            Не нормально. И именно поэтому я не перевариваю кофескрипт, там всё ещё в разы хуже (хотя и признаю, что в нем есть некоторые интересные фишки).


            1. batmandarkside
              14.10.2015 21:46
              +4

              Нормально


            1. Eternalko
              15.10.2015 02:20

              Сравнения ради примерно так выглядит похожий код в CS

              ST3



              1. dom1n1k
                15.10.2015 03:29
                -1

                Почти полное отсутствие скобок (как круглых, так и фигурных), давая смешную экономию на количестве символов, убивает читаемость на корню. Я вынужден всматриваться в строку, чтобы для начала понять, а где тут вообще что? Работать парсером, фактически. Где ключевое слово, где имя функции, где параметр, где переменные? И даже подсветка не спасает.


                1. Eternalko
                  15.10.2015 04:40

                  С точки зрения людей пишущих на CS отсутствие лишних символов и пустых слов (типа function) увеличивает читаемость.


                1. ankh1989
                  15.10.2015 08:08
                  +2

                  Первый раз вижу CS. Отлично читается.


                1. faiwer
                  15.10.2015 08:22

                  Отсутствие фигурных скобок, на мой взгляд, тут только увеличивает читаемость. Как и в ruby и в python-е. Возможно просто дело привычки. А вот слабость подсветки на лицо. Действительно бедная палитра. На моих скриншотах снизу больше «светофора».


                  1. Eternalko
                    15.10.2015 11:01

                    У меня она специально выключена.


        1. faiwer
          14.10.2015 20:08

          Ну это уже от подсветки зависит. У меня в st3 "=>" выделяется добротно и глаза за него цепляются не хуже, чем за function. А вот раздражает он куда меньше.

          скриншот
          image


          1. dom1n1k
            15.10.2015 03:36

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


            1. faiwer
              15.10.2015 08:20

              Я Код от балды написал. Зря вы к нему цепляетесь :) Ключевой момент, что без египетских кавычек и хорошей цветовой схеме, всё более чем наглядно.

              Ещё скриншот
              image


      1. 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);
        


        1. Mithgol
          15.10.2015 08:23

          Вокруг единственного аргумента можно не ставить скобки:

          const findUser = id => User.findOne({where: {id}})
          const onSuccess = user => done(null, user)
          


          1. hell0w0rd
            15.10.2015 08:55
            +1

            По кодстайлу, на пример, Airbnb, так делать запрещено. На мой взгляд вот как раз эта возможность и ее использование может ухудшить читаемость


            1. everyonesdesign
              15.10.2015 12:05
              +2

              Неправда, у них сказано как раз наоборот:

              If your function only takes a single argument, feel free to omit the parentheses.


              1. hell0w0rd
                15.10.2015 13:12
                +1

                Хм. Время идет, люди и гады меняются. Изначально было не так: github.com/airbnb/javascript/blob/58cdc731f435db78f3c1f4d466284796128fb75b/README.md#arrow-functions
                Я все еще придерживаюсь старого гайда. Они еще перешли на trailing comma в описание объекта, что мне вообще не нравится.


                1. trikadin
                  15.10.2015 19:45

                  Наверное, люди и гайды?)

                  От себя замечу, что считаю более разумным всегда ставить круглые скобки вокруг аргументов — во-первых, это увеличивает консистентность кода, во-вторых, количество аргументов может увеличиться.


                  1. hell0w0rd
                    15.10.2015 21:04

                    Забавная очепятка.
                    Да, полностью согласен. Можно такого написать, что пару минут вчитываться прийдется.

                    const foo = a => b => c => a + b + c;
                    


                    1. trikadin
                      15.10.2015 21:13

                      Да не, такого-то ни один разумный (во всяком случае, слышавший о правиле маньяка) программист писать не будет. А вот не ставить скобки соблазн может быть огромный. Но всё же считаю это плохой практикой.

                      Хотя я тоже не без греха. Например, у меня в коде иногда бывают такие штуки:

                      const values = $C(obj).reduce((result, value) => (result.push(value), result), []);
                      


                      $C, если что — это от библиотечки, которая предоставляет одинаковый набор методов для любых коллекций.


                    1. var-log
                      17.10.2015 19:03
                      +1

                      Типичное каррирование, ИМХО оно всегда нечитабельное


      1. Mithgol
        15.10.2015 08:20

        И что тут не так?
        Вместо «(user)» можно просто «user» было бы записать.

        (Не ошибка, но скобки лишние.)


  1. hell0w0rd
    14.10.2015 18:14
    +4

    У arrow-function не просто нет this, у них нет прототипа.


    1. Arilas
      15.10.2015 00:44

      При этом, даже создатели V8 заявляли, Arrow Functions инициализируются быстрее, вызов быстрее так как:
      1. Не создается прототип
      2. Не создается arguments
      3. Не нужно регистрировать ее как функцию конструктор (нельзя через new писать)


      1. hell0w0rd
        15.10.2015 08:51

        Ну это понятно. Просто все эти особенности хорошо бы учитывать.


  1. SamVimes
    15.10.2015 20:08

    Возможно глупый вопрос, но почему лямбды называют стрелочными функциями?


    1. faiwer
      15.10.2015 20:14
      +2

      Скажите, а что такое лямбда? Описание на той же вики, не очень проясняет вопрос. Лямбда обязана быть однострочной? Если да, то стрелочные функции — не обязаны. Лямбда это просто анонимный метод, определённый прямо посреди кода? Если так, то и обычные function-ы, без явно заданного имени, получается, тоже лямбды?


      1. SamVimes
        16.10.2015 16:42
        +1

        Ну, вопрос хороший, а я, к сожалению, не эксперт, но мне казалось, что в лямбде отсутствует контекст, т.е. (вроде как) используется контекст того «места», где лямбда создана. Опять таки боюсь ошибиться, но у function контекст есть.


        1. faiwer
          16.10.2015 22:07
          +1

          Ну по такому критерию получается что да, это лямбды.


      1. kvas
        20.10.2015 12:28

        Обычно лямбда или лямбда функция — это программистский слэнг, означающий «анонимная функция». Изначально это пошло из лямбда-исчисления, где вычисления представляются в виде лямбда-форм, которые по сути дела являются анонимными функциями. Во многих языках программирования ключевое слово lambda используется для определения анонимных функций, поэтому их стали называть лямбдами, иногда даже в языках где такого ключевого слова нет.

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