Пользователь
11,2
рейтинг
14 октября 2015 в 12:28

Разработка → Введение в стрелочные функции (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(Андрей Хахарев)
Перевод: Alex Gorbatchev
Сергей Грибняк @greebn9k
карма
22,0
рейтинг 11,2
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (45)

  • +5
    Стрелочные функции (Arrow functions) в ECMAScript 6 — и название такое же, и тема раскрыта полнее.
    • +1
      Спасибо за полезную ссылку. Пригодится тем, кто ознакомится с этой статьей.
  • 0
    Вы бы поделились с silmarilion'ом и дали ему хоть один совместный перевод от своего имени опубликовать. Хотел Вашего соавтора плюсануть на Мегамозге, а у него там нет ни одной публикации. Здесь он вообще ридонли.
    • +1
      Учтем. Спасибо.
  • 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'е есть ещё обычные анонимные функции, которые тоже в общем-то лямбды, несмотря на их развесистый синтаксис. Возможно поэтому тут используется более специфичный термин «стрелочные функции».

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