company_banner

5 рекомендаций по написанию качественных стрелочных функций

Автор оригинала: Dmitri Pavlutin
  • Перевод
Стрелочные функции в JavaScript довольно популярны. И они этого заслуживают, отличаясь лаконичным синтаксисом, лексической привязкой this и тем, что их очень удобно использовать в качестве коллбэков.



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

1. Пользуйтесь выводом имён стрелочных функций


Стрелочные функции в JavaScript анонимны: их свойство name содержит пустую строку — ''.

( number => number + 1 ).name; // => ''

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

Вот один из моментов отладки кода, в котором вызываются анонимные функции.


Отладка кода, содержащего анонимные функции

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

К счастью, в JavaScript имеется механизм выведения имён функций (возможность ES2015), который способен, в определённых условиях, обнаруживать имя функции. Идея вывода имени заключается в том, что JavaScript может выяснить имя стрелочной функции из её синтаксической позиции, то есть — на основании имени переменной, хранящей ссылку на объект функции.

Посмотрим на механизм выведения имён функций в действии:

const increaseNumber = number => number + 1;

increaseNumber.name; // => 'increaseNumber'

Так как ссылка на стрелочную функцию хранится в переменной increaseNumber, JavaScript решает, что имя 'increaseNumber' хорошо подойдёт для функции. В результате стрелочная функция и получает такое имя.

Рекомендуется использовать механизм выведения имён функций для именования стрелочных функций.

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


Отладка кода, содержащего стрелочные функции, которым даны имена

Так как теперь у стрелочных функций есть имена, стек вызовов даёт больше полезных сведений о выполняемом коде:

  • Имя функции handleButtonClick указывает на вызов обработчика события click.
  • Имя increaseCounter указывает на вызов функции, увеличивающей счётчик.

2. Всегда, когда это возможно, стремитесь писать встраиваемые функции


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

Например, вот «полная» форма стрелочной функции:

const array = [1, 2, 3];

array.map((number) => { 
  return number * 2;
});

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

const array = [1, 2, 3];

array.map(number => number * 2);

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

3. Осторожно используйте стрелочные функции и операторы сравнения


Операторы сравнения >, <, <= и >= очень похожи на стрелку =>, с помощью которой объявляют стрелочные функции (такие стрелки ещё называют «жирными стрелками»).

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

Объявим стрелочную функцию, в которой используется оператор <=:

const negativeToZero = number => number <= 0 ? 0 : number;

Наличие в одной строке символов => и <= дезориентирует читателя кода.

Для того чтобы чётко отличать стрелки от операторов сравнения, можно воспользоваться несколькими подходами.

Во-первых, можно заключить выражение в круглые скобки:

const negativeToZero = number => (number <= 0 ? 0 : number);

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

const negativeToZero = number => {
  return number <= 0 ? 0 : number;
};

Эти преобразования снимают ту неопределённость, которую вызывает использование в одной строке и символа стрелки и операторов сравнения.

Рекомендуется, если однострочная стрелочная функция содержит операторы >, <, <= и >=, заключать соответствующие выражения в круглые скобки или пользоваться многострочной формой объявления стрелочной функции.

4. Используйте скобки или многострочные конструкции при создании простых объектов в стрелочных функциях


Использование объектного литерала внутри встраиваемой стрелочной функции приведёт к появлению синтаксической ошибки:

const array = [1, 2, 3];

// выбрасывает SyntaxError!
array.map(number => { 'number': number });

JavaScript считает фигурные скобки блоком кода, а не объектным литералом.

Если же заключить объектный литерал в круглые скобки — эта проблема будет решена:

const array = [1, 2, 3];

// Работает!
array.map(number => ({ 'number': number }));

Если в объектном литерале имеется много свойств, то тут даже можно использовать переводы строк. Стрелочная функция при этом всё ещё остаётся встраиваемой:

const array = [1, 2, 3];

// Работает!
array.map(number => ({
  'number': number
  'propA': 'value A',
  'propB': 'value B'
}));

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

5. Внимательно относитесь к слишком глубоким вложенным конструкциям


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

Рассмотрим следующий сценарий. Когда нажимают на кнопку — выполняется запрос к серверу. Когда получают ответ сервера, содержащий набор неких элементов, имена этих элементов выводятся в консоль:

myButton.addEventListener('click', () => {
  fetch('/items.json')
    .then(response => response.json())
    .then(json => {
      json.forEach(item => {
        console.log(item.name);
      });
    });
});

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

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

Первый подход заключается в том, чтобы записать ссылки на функции в переменные. Имена переменных должны кратко описывать сущность функции (взгляните на рекомендацию №1, посвящённую выводу имён функций).

const readItemsJson = json => {
  json.forEach(item => console.log(item.name));
};

const handleButtonClick = () => {
  fetch('/items.json')
    .then(response => response.json());
    .then(readItemsJson);
};

myButton.addEventListener('click', handleButtonClick);

В ходе рефакторинга мы извлекли вложенные стрелочные функции и записали их в переменные readItemsJson и handleButtonClick. Уровень вложенности кода снизился с 3 до 2. Теперь код стал более понятным.

Ещё один вариант рефакторинга этого кода заключается в переводе всей функции в формат async/await. Это — отличный способ решения проблемы вложенных функций:

const handleButtonClick = async () => {
  const response = await fetch('/items.json');
  const json = await response.json();
  json.forEach(item => console.log(item.name));
};

myButton.addEventListener('click', handleButtonClick);

Рекомендуется избегать слишком глубоких уровней вложенности стрелочных функций, извлекая их в переменные в виде отдельных функций, или, если это возможно, применяя синтаксис async/await.

Итоги


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

Встраиваемые стрелочные функции удобны в тех случаях, когда тело функции содержит лишь одно выражение.

Операторы >, <, <= и >= похожи на стрелку =>, используемую при объявлении стрелочных функций. Когда эти операторы используются в теле встраиваемой стрелочной функции, стоит задуматься о преобразовании кода.

Синтаксис объектных литералов, наподобие { prop: 'value' }, похож на блок кода {}. В результате, когда объектный литерал размещается внутри встраиваемой стрелочной функции, его нужно заключить в круглые скобки: () => ({ prop: 'value' }).

Слишком большие уровни вложенности функций запутывают того, кто читает код. Рекомендуется уменьшать уровень вложенности функций, извлекая их и записывая в переменные. Ещё один подход к уменьшению уровня вложенности кода заключается в применении конструкции async/await.

Уважаемые читатели! Знаете какие-нибудь интересные приёмы использования стрелочных функций?

RUVDS.com
RUVDS – хостинг VDS/VPS серверов

Похожие публикации

Комментарии 10

    +3
    — Фу, статья про стрелочный функции!
    — Фу, опять статья про стрелочные функции!
    — Вау, статья про стрелочные функци!
    ©
      +2

      Пока такие статьи плюсуют, они будут появляться.

      +2
      Небольшие ремарки по коду.

      — Зачем объявлять проперти объекта как строка? Это не JSON файл. Убираем строку и получаем
      array.map(number => ({ number: number }))


      — Используем возможности ES6 и переписываем строку выше
      array.map(number => ({ number }))


      — На дворе уже 2020 год можно и не использовать цепочки вызовов в промисах.
      
      myButton.addEventListener('click', async () => {
        let response;
      
        try {
          response = await fetch('/items.json');
        } catch (err) {
          // do something to check err
          console.log('error ->', err);
          throw err;
        }
      
        response = await response.json();
      
        console.log(response.name);
      });
      
        +1
        Плохо знаю JavaScript, поэтому просто спрошу, т.к. глаз резануло:
         fetch('/items.json')
            .then(response => response.json()); <===
            .then(readItemsJson);

        Это нормально, так писать цепочку?
          0

          Это явно опечатка, в таком виде это синтаксическая ошибка.

          0
          Интересно, зачем было придумывать новое название анонимным функциям, или в стрелочных есть какое то отличие? Маркетинговый ход?
            0

            Разумеется, есть: у стрелочной функции, в отличии от анонимной, нет своих this и arguments.

              0

              Классическая анонимная функция:


              var f = function () {};
              // ES5: f.name = ""

              Стрелочная функция:


              const f = () => {};

              Ранее механизма вывода имени не было. Однако есть семантические различия между объявлениями функции через function и через стрелку. Например, у стрелочных функций нет своего контекста, к которому есть доступ через this. Поэтому они не могут выступать в качестве конструктора.

              0
              Какое рассуждение стоит за рекомендацией использовать стерлочные функции cостоящие из одного выражения? Оптимизация компилирования? Памяти? Назначенная семантика какая-то?
                0

                Конкретно к стрелочным функциям тут относится только первая рекомендация, которая как бы объяснение очередной черной магии JavaScript. Остальное — общеприменимый здравый смысл, и стрелочки тут ни при чем.


                А надо было… уделить больше внимания отличию стрелочных от старых анонимных, и в особенности автоматической привязке this. Я как-то спорил с однокурсником на тему какой синтаксис использовать; пришлось хорошенько погуглить и разобраться в теме, чтобы понять, что не просто так из прихоти в реактах пишут


                const callback = (ev) => {
                   ...
                }

                вместо старого всем понятного


                function callback(ev) {
                    ...
                }

                (или как это было бы внутри класса)


                class A {
                    callback(ev) {
                        ...
                    }
                }

                А вот и сама заметка: https://t.me/ratijas_life/16

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

                Самое читаемое