Привет, Хабр! Представляю вашему вниманию перевод статьи "When (and why) you should use ES6 arrow functions?—?and when you shouldn’t" автора Cynthia Lee.

Стрелочные функции — наиболее популярная фишка ES6. Это новый, лаконичный способ написания функций.

function timesTwo(params) {
  return params * 2
}
timesTwo(4);  // 8

Теперь то же самое при помощи стрелочной функции.

var timesTwo = params => params * 2
timesTwo(4);  // 8

Намного короче! Мы можем опустить фигурные скобки и оператор return ( если нет блока, но об этом позже).

Давайте разберемся, чем отличается новый способ от привычного.

Синтаксис


Первое, на что вы быстро обратите внимание, различные вариации синтаксиса. Давайте посмотрим на основные:

1. Без параметров


Если у функции нет параметров, вы можете просто написать пустые круглые скобки перед =>

() => 42

На самом деле, можно вообще без скобок!

_ => 42

1. Один параметр


Круглые скобки тоже не обязательны

x => 42  || (x) => 42

3. Несколько параметров


Вот тут уже нужны скобки

(x, y) => 42

4. Инструкции


Обычно функциональное выражение возвращает значение, в то время как инструкция отвечает за действие.

Нужно помнить, что в случае со стрелочными функциями, если у нас есть какой-то набор действий/инструкций, нужно обязательно использовать фигурные скобки и оператор return.
Вот пример стрелочной функции, используемой с оператором if:

var feedTheCat = (cat) => {
  if (cat === 'hungry') {
    return 'Feed the cat';
  } else {
    return 'Do not feed the cat';
  }
}

5. Тело фунцкии — блок


Если даже ваша функция просто возвращает значения, но ее тело находится в фигурных скобках, оператор return нужен обязательно.

var addValues = (x, y) => {
  return x + y
}

6. Литерал объекта


Если функция возвращает объектный литерал, его нужно заключить в круглые скобки.

x =>({ y: x })

Стрелочные функции — анонимные


Обратите внимание, что стрелочные функции – анонимны, у них нет имени.

Это создает некоторые сложности:

  1. Трудно дебажить

    Когда произойдет, вы не сможете отследить имя функции и номер строки, где произошла ошибка.
  2. Нельзя присвоить переменной

    Если вам нужна ссылка внутри функции на саму себя для чего-то (рекурсия, обработчик событий, который необходимо отменить), ничего не выйдет

Главное преимущество: нет своего this


В обычных функциях this указывает на контекст, в котором эта функция вызвана. this стрелочной функции такой же как this окружения, в котором объявлена стрелочная функция.

Например, посмотрите на функцию setTimeout ниже:

// ES5
var obj = {
  id: 42,
  counter: function counter() {
    setTimeout(function() {
      console.log(this.id);
    }.bind(this), 1000);
  }
};

В примере выше требуется использовать .bind(this), чтобы передать контекст в функцию. Иначе this будет undefined.

// ES6
var obj = {
  id: 42,
  counter: function counter() {
    setTimeout(() => {
      console.log(this.id);
    }, 1000);
  }
};

В этом примере не нужно привязывать this. Стрелочная функция возьмет значение this из замыкания.

Когда не следует использовать стрелочные функции


Теперь, думаю, стало понятно, что стрелочные функции не заменяют обычные.

Вот несколько примеров, когда вам вряд ли захочется их использовать.

1. Методы объекта


Когда вы вызываете cat.jumps, количество жизней не уменьшается. Это происходит потому, что this не привязан ни к чему, и наследует значение из замыкания.

var cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}

2. Функции обратного вызова с динамическим контекстом


Если вам нужен динамический контекст, стрелочная функция — плохой вариант.

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

Если нажать на кнопку, мы получим TypeError. Это связано с тем, что this не привязан к кнопке.

3. Когда ухудшается читаемость кода


С обычными функциями всегда понятно, что вы хотели сказать. Со стрелочными, учитывая разнообразные варианты синтаксиса, некоторые вещи становятся менее очевидными.

Когда точно стоит использовать стрелочные функции


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

Также мне очень нравится использовать стрелочные функции во всяких map и reduce — код так лучше читается.

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


  1. yea
    13.06.2018 10:46
    +2

    Нельзя присвоить переменной

    Если вам нужна ссылка на функцию для чего-то (рекурсия, обработчик событий, который необходимо отменить), ничего не выйдет


    В оригинале не «ссылка на функцию», а именно «self-reference», ссылка внутри функции на саму себя. Соответственно, обработчик событий там рассматривается такой, который сам себя должен при каких-то условиях отвязать. А с самой ссылкой на функцию как раз всё в порядке.


    1. KiraJS Автор
      13.06.2018 11:59

      Спасибо, поправила.


  1. Wroud
    13.06.2018 12:16

    this стрелочной функции такой же как this окружения, в котором вызвана стрелочная функция.

    Скорее создана или объявлена, а не вызвана


    1. KiraJS Автор
      13.06.2018 14:29

      Согласна, исправила


  1. Dimd13
    13.06.2018 12:18
    -1

    var cat = {
      lives: 9,
      jumps: () => {
        this.lives--;
      }
    }
    ES6 Определение методов объекта

    var cat = {
      lives: 9,
      jumps() {
        this.lives--;
      }
    };
    console.log(cat.lives); // => 9
    cat.jumps();
    console.log(cat.lives); // => 8
    


    1. kahi4
      13.06.2018 12:53

      Это сахар для конструкции


      var cat = {
         lives: 9,
         jumps: function() {
           this.lives--;
         }
      };

      Так что все правильно. При этом this так же потеряется при передаче cat.jumps как переменной.


      var cat = {
        lives: 9,
        jumps() {
          this.lives--;
        }
      };
      console.log(cat.lives); // => 9
      const jump = cat.jumps;
      jump();
      console.log(cat.lives); // => все еще 9


  1. Fragster
    13.06.2018 13:38

    Почему во всех примерах с this используют setTimeout, но не используют его третий параметр?


    1. lazyboa
      13.06.2018 16:43

      Не работает в IE.
      Он, конечно, уже почти умер.


      1. Chamie
        13.06.2018 17:08
        +1

        Так он и стрелочные функции не поддерживает.


    1. justboris
      14.06.2018 02:30

      Третий параметр в setTimeout — это аргумент для функции, а не контекст.


      setTimeout(function(that) {
        console.log(that.id);
      }, 1000, this);

      Придется вводить новую переменную по имени that/self и т.п. Стрелочные функции позволяют оставить this как есть, и не назначать ему дополнительных алиасов


  1. Juma
    13.06.2018 15:16

    Когда точно стоит использовать стрелочные функции
    JS такая штука, что каждый его пишет как ему удобно.
    Но все же добавлю, что стрелочные функции очень хорошо помогают внутри классов (и в других подобных структурах). Так как устраняют заморочки с this.


  1. Sabubu
    13.06.2018 16:18

    Какой ужасный синтаксис. Люди почему-то бездумно используют эти стрелочные функции, не понимая, для чего они придуманы, и думая, что «обычные» функции «устарели в 2018» (да, манера писать «xxx в 2018» выдает неопытного кодера).

    Эти функции были придуманы в первую очередь для случаев, когда надо что-то преобразовать, передав произвольное выражение или условие, например:

    var heavyGoods = goods.filter(g => g.getWeight() > 100);
    var names = users.map(u => u.name);
    


    Получается аккуратный и лаконичный код. Он хорошо читается: «Отбери товары, у которых вес больше 100 чего-то». Но если у вас «обычная» функция, то лучше писать ее с помощью «классического синтаксиса»:

    
    function feedTheCat(cat) {
      if (cat === 'hungry') {
        return 'Feed the cat';
      } else {
        return 'Do not feed the cat';
      }
    }
    


    Тут все понятно: мы, прочитав первое слово, видим, что перед нами функция. А прочитав первую строку, знаем ее прототип. Сравните это с уродливым примером из статьи:

    
    var feedTheCat = (cat) => {
      if (cat === 'hungry') {
        return 'Feed the cat';
      } else {
        return 'Do not feed the cat';
      }
    }
    


    Попробуйте прочитать начало функции: «переменная feedTheCat равна проекции cat на блок кода». Такое ощущение, что мы видим результат работы обфускатора. Более того, я иногда вижу, как такие функции вкладывают друг в друга для большего ада или пишут несколько стрелок: (x) => (y) => { 100 строк кода };

    Кому в здравом уме придет в голову объявлять функцию как переменную?

    Я подозреваю, что люди пытаются таким образом пародировать языки вроде Хаскелл в своем коде. Это неправильно. Если вам нравится Хаскелл, то просто пишите на нем, а не превращайте код в нечитаемую лапшу.

    Надо помнить, что код пишется для людей, чтобы его было удобно читать, а не для того, чтобы похвастаться, что вы читаете сайты для фронтендеров.


    1. Chamie
      13.06.2018 17:19
      +1

      Сравните это с уродливым примером из статьи:
      var feedTheCat = (cat) => {
        if (cat === 'hungry') {
          return 'Feed the cat';
        } else {
          return 'Do not feed the cat';
        }
      }
      Тут вообще пример ради примера. Если уж делать стрелочной функцией, то через тернарный оператор (и без скобок вокруг единственного аргумента):
      var feedTheCat = cat => cat === 'hungry' ? 'Feed the cat': 'Do not feed the cat'

      Но всё равно остаётся странным, что кошка равна строке, а функция «покормить кошку» возвращает строку, но не делает самого действия. С ходу красивого названия не придумывается, но должно быть скорее как-то так:
      const shouldIFeedTheCat = cat => cat.isHungry ? 'Feed the cat': 'Do not feed the cat';


      1. Sabubu
        14.06.2018 17:18

        Вы попробуйте прочитать начало вашего кода:

        var feedTheCat = cat => cat === 'hungry' ...


        5 знаков равенства! «Переменная feedTheCat равна проекции переменной cat на выражение, если cat равняется 'hungry'....». какая-то галиматья, если честно. Не стоит так уплотнять код.

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

        И думаю, что не надо заменять иф тернарным оператором. Они оба существуют не просто так.


    1. argonavtt
      14.06.2018 16:15

      Стрелочные функции отлично взаимодействуют с классами в первую очередь, нет ни какого смысла биндить this каждый раз когда ты пишешь новую функцию, это не удобно и требует постоянного повторения. И да это синтаксический сахар, такой же как и сами классы. Суть в том, что у js куча продуктов, и на каждом пишут по своему. Я лично с удовольствием использую стрелочные функции в React, и не вижу ни 1 причины что бы перестать их использовать.


      1. Sabubu
        14.06.2018 17:23

        Значит надо было не увлекаться этими стрелочными функциями, а исправить проблему в ключевом слове function.

        Я предпочитаю решать проблему this так: 1) использовать that или self 2) не вкладывать несколько функций друг в друга и вообще не писать код с большой (больше 3-4) глубиной отступов. Коллбеки нужны не так уж и часто и уж точно нет необходимости вкладывать несколько функций друг в друга.

        Надо думать о тех, кто будет потом читать ваш код и кому придется разбираться в этом нагромождении стрелок и скобок. Талант программиста заключается, на мой взгляд, именно в умении писать понятно, а не демонстрировать знание всех возможных фич языка.


        1. argonavtt
          14.06.2018 17:38

          Значит надо было не увлекаться этими стрелочными функциями, а исправить проблему в ключевом слове function.

          Вы хоть представляете что бы было если бы что то поменяли с самим словом? Сколько старого кода стало бы работать по другому.

          Вложенность тут не при чём, стрелочные функции были придуманы в 1 очередь для работы с классами, необходимость иметь свой this в данном случае нет, т.к. в 99% вы будете ссылаться на this класса в котором работаете. Все ваше замечания по сути дела правильны пока не доходит дела до самого главного для чего и были придуманы стрелочные функции.


  1. paratagas
    13.06.2018 17:26
    +4

    Еще можно упомянуть о том, что стрелочные функции не имеют собственного объекта arguments. И в в теле стрелочных функций arguments будет ссылаться на переменную в окружающей области видимости, будь то внешняя функция или глобальный скоуп.


    1. Aries_ua
      14.06.2018 15:27

      Но нас спасет:

      (...args) => { console.log(...args) }


  1. Paran01d
    13.06.2018 19:38
    +1

    Скоро на JS станет писать write-only код легче, чем на Perl)
    Не холивара ради, но, имхо, яву должен подталкивать писать легко читаемый код, а не превращать это в удел гуру, постигшего науку применять сахар и особенности синтаксиса там, где это уместно.
    ЗЫ: Наверное по этому сменил Perl на Python 5 лет назад, хотя до этого Perl привлекал меня именно синтаксической необузданностью.


  1. x-foby
    14.06.2018 08:59
    +2

    Высосанные из пальца примеры — это, конечно, наше всё))

    var button = document.getElementById('press');
    button.addEventListener('click', () => {
        this.classList.toggle('on');
    });

    Зачем здесь использовать this? Зачем мы объявляли button, если потом не используем? Забавно, конечно)
    var button = document.getElementById('press');
    button.addEventListener('click', () => button.classList.toggle('on'));
    


    1. Sabubu
      14.06.2018 17:25

      button.addEventListener('click', function (e) { self.onButtonClick(e); });


      На мой взгляд будет лучше. При желании функцию можно писать не в 1 строчку, а с переносами в три.


  1. aleksandy
    14.06.2018 11:21

    По-моему, тут вообще замыкания не нужно. Можно же ссылку на кнопку получить из события.

    document.getElementById('press').addEventListener('click', e => e.target.classList.toggle('on'));


  1. iShatokhin
    14.06.2018 15:57

    Трудно дебажить

    Когда произойдет, вы не сможете отследить имя функции и номер строки, где произошла ошибка.

    Не совсем так.
    (() => {
         throw new Error('Some error');
    })();
    
    // Uncaught Error: Some error
    //    at <anonymous>:2:12 <--- Номер строки никуда не делся
    
    const funcName = () => {
        throw new Error('Some error');
    };
    
    funcName ();
    
    // Uncaught Error: Some error
    //    at funcName (<anonymous>:2:11) <--- А теперь еще и имя появилось
    
    funcName.name; 
    // "funcName"