Javascript лямбда стрелочные функции
Существует ещё более простой и краткий синтаксис для создания функций, который часто лучше, чем синтаксис Function Expression.
Он называется «функции-стрелки» или «стрелочные функции» (arrow functions), т.к. выглядит следующим образом:
…Такой код создаёт функцию func с аргументами arg1..argN и вычисляет expression с правой стороны с их использованием, возвращая результат.
Другими словами, это более короткий вариант такой записи:
Давайте взглянем на конкретный пример:
То есть, (a, b) => a + b задаёт функцию с двумя аргументами a и b , которая при запуске вычисляет выражение справа a + b и возвращает его результат.
Если у нас только один аргумент, то круглые скобки вокруг параметров можно опустить, сделав запись ещё короче:
Если нет аргументов, указываются пустые круглые скобки:
Функции-стрелки могут быть использованы так же, как и Function Expression.
Например, для динамического создания функции:
Поначалу функции-стрелки могут показаться необычными и трудночитаемыми, но это быстро пройдёт, как только глаза привыкнут к этим конструкциям.
Они очень удобны для простых однострочных действий, когда лень писать много букв.
Многострочные стрелочные функции
В примерах выше аргументы использовались слева от => , а справа вычислялось выражение с их значениями.
Порой нам нужно что-то посложнее, например, выполнить несколько инструкций. Это также возможно, нужно лишь заключить инструкции в фигурные скобки. И использовать return внутри них, как в обычной функции.
Здесь мы рассмотрели функции-стрелки как способ писать меньше букв. Но это далеко не всё!
Стрелочные функции обладают другими интересными особенностями. Их изучение требует знания некоторых других возможностей языка JavaScript, поэтому мы вернёмся к стрелочным функциям позже, в главе Повторяем стрелочные функции.
А пока мы можем использовать их для простых однострочных действий и колбэков.
Итого
Функции-стрелки очень удобны для однострочных действий. Они бывают двух типов:
Недавно мы делали проект с колесом фортуны. И был там странный способ определять функции — стрелочный. Это способ сэкономить строки кода и сделать код читаемым, но также с его помощью можно превратить код в кашу. Разберём, как устроены стрелочные функции в JavaScript.
О чём речь
В проекте с колесом у нас был такой фрагмент кода:
В комментарии написано, что мы объявляем функцию, но в коде нет стандартной формы объявления function()<> . Вместо этого идёт константа, потом оператор присваивания, скобки, стрелка и какой-то код. Так вот, это и есть пример объявления стрелочной функции. Сейчас объясним подробнее.
Как объявить стрелочную функцию
Чтобы объявить стрелочную функцию, делают так:
let ИмяФункции = (Аргумент1, Аргумент2…) => ДействияФункции
Вместо let можно использовать const — результат будет такой же. Получается, что мы как бы объявляем переменную, которая что-то умеет делать. Чтобы использовать такую переменную, нужно написать её имя и в скобках передать аргументы — точно так же, как и при использовании обычной функции.
Вот самый простой пример использования стрелочной функции:
let sum = (a, b) => a + b;
Здесь мы объявили функцию sum(), которой передаются два аргумента. Внутри функции они складываются, а результат сложения возвращается при вызове функции. По этой причине вызов sum(2,3) вернёт число 5.
Если нужно внутри функции выполнить более одной команды, то используют фигурные скобки:
Чем стрелочные функции отличаются от обычных
На самом деле отличий почти нет — это такие же функции, как и обычные. Например, let sum = (a, b) => a + b; — это то же самое, как если бы мы сделали так:
let sum = function(a, b) <
Всё дело — в краткости записи, чтобы писать иногда более изящный код. Но если стрелочные функции делают код менее читаемым — то лучше их не использовать и объявлять функции как обычно.
👉 Единственное, чем на самом деле отличаются стрелочные и обычные функции, — это возможность пробросить метод объекта this снаружи, а не обращаться к его вложенному значению внутри функции. Но если вы уже дошли в проектах до таких тонкостей — вы уже знаете достаточно, чтобы разобраться в этом самостоятельно, коллега.
Динамическое создание функций
Действительно полезное свойство стрелочных функций — возможность выбрать содержимое функции в зависимости от значений других переменных.
Допустим, если мы пишем вежливого чат-бота, то делаем такую логику:
- спрашиваем, сколько человеку лет;
- если ему меньше 18, то говорим ему «Привет»;
- иначе говорим «Здравствуйте».
Запишем это в виде стрелочной функции:
Зачем они нужны, если есть обычные?
Если вы только начинаете осваивать программирование, то разницы между ними нет. В этом случае используйте обычные функции — в них меньше шансов сделать ошибку.
Но если вы уже прокачались и изучаете или пробуете писать сложные проекты, где нужно обращаться к методу this внутри функции, используя свойства внешнего объекта, — то да, лучше использовать стрелочные функции.
Вывод простой: если вы задаёте себе этот вопрос — вам пока не нужны стрелочные функции. Но знать о них стоит уже сейчас.
Стрелочные функции (arrow functions) появились в 6-й редакции Javascript, более известной как ECMAScript 6. Сегодня их можно встретить практически в любом современном приложении, написанном на любом из современных JS фреймворков.
Я бы выделил 3 главных преимущества использования стрелочных функций:
Давайте разберем эти преимущества более детально.
Предположим у нас есть массив данных:
При выводе результата для каждого элемента исходного массива мы использовали обратные кавычки (backticks), которые позволяют совмещать написание строчных значений и переменных.
В этом примере мы использовали обычную функцию. Теперь давайте посмотрим, как будет выглядеть наше выражение, написанное с использованием стрелочной функции.
Cтрелочные Функции JS
Для начала давайте уберем слово function и добавим символ стрелочной функции => . Таким образом мы получим выражение следующего вида:
Наша функция принимает один единственный аргумент – fruit . Следовательно, мы можем избавиться от скобок вокруг него.
Также мы можем использовать так называемый implicit return (подразумеваемое возвращение…). Явное возвращение (explicit return) подразумевает использование слова return при возврате результата. Если ваша функция возвращает какой-то один результат, то написание слова return можно опустить.
В результате получаем:
Что мы изменили?
- Удалили слово return
- Записали все выражение в одну строчку
- Удалили фигурные <> скобки
После удаления слова return и фигурных скобок мы получаем implicit return, и нам не требуется уточнять, что наша функция возвращает fresh $
Что eсли стрелочная функция не принимает никаких аргументов?
В случаях, когда в функцию не требуется передавать никаких аргументов необходимо оставлять пустые скобки:
Некоторые разработчики вместо пустых скобок используют переменную _ , что имеет по большей мере эстетическое значение.
Лично я предпочитаю использовать () => вместо (_) => , когда в функцию не требуется передовать аргументы.
Все стрелочные функции - безымянные
Для начала, приведу пример функции с указанием названия:
Одним из преимуществ такой функции является, то, что в случае ошибки, мы получаем название функции, где она произошла.
Стрелочные функции являются анонимными, то есть им нельзя присвоить название.
С другой стороны, стрелочную функцию можно присвоить переменной. В этом примере мы объявляем стрелочную функцию и присваиваем ее переменной, используя function expression:
Если, у вас нет жестких требований к более детальному отображению ошибок (с указанием названия функции), смело используйте стрелочные функции!
Значение this в стрелочных функциях
Внутри обычной функции значение this зависит от того, как была вызвана функция.
Например, при обычном выполнении функции, значение this - глобальный объект.
Если вы вызываем функцию объекта, то значением this является сам объект.
У стрелочных функций отсутствует собственный контекст выполнения, следовательно, значение this в стрелочных функциях всегда наследуется от родительской (внешней) функции.
В этом примере мы получим undefined для каждого числа в массиве numbers:
Значение this , которое мы получаем внутри console.log(this.message, number) определяется обычной функцией. Эта функция не привязана к нашему объекту, поэтому получаем - undefined.
Второй вариант - использовать стрелочную функцию.
Значение this внутри обыкновенной функции - зависит от контекста вызова. Значение this в стрелочной функции всегда принимает значение внешней функции!
Одной из самых заметных новшеств современного JavaScript стало появление стрелочных функций (arrow function), которые иногда называют «толстыми» стрелочными функциями (fat arrow function). При объявлении таких функций используют особую комбинацию символов — => .
У стрелочных функций есть два основных преимущества перед традиционными функциями. Первое — это очень удобный и компактный синтаксис. Второе заключается в том, что подход к работе со значением this в стрелочных функциях выглядит интуитивно понятнее, чем в обычных функциях.
Иногда эти и другие преимущества ведут к тому, что стрелочному синтаксису отдают безусловное предпочтение перед другими способами объявления функций. Например, популярная конфигурации eslint от Airbnb принуждает к тому, чтобы всегда, когда создают анонимную функцию, такая функция была бы стрелочной.
Однако, как и у других концепций и механизмов, используемых в программировании, у стрелочных функций есть свои плюсы и минусы. Их применение способно вызывать негативные побочные эффекты. Для того чтобы пользоваться стрелочными функциями правильно, необходимо знать о возможных проблемах, связанных с ними.
В материале, перевод которого мы сегодня публикуем, речь пойдёт о том, как работают стрелочные функции. Здесь будут рассмотрены ситуации, в которых их использование способно улучшить код, и ситуации, в которых их применять не стоит.
Особенности стрелочных функций в JavaScript
Стрелочные функции в JavaScript — это нечто вроде лямбда-функций в Python и блоков в Ruby.
Это — анонимные функции с особым синтаксисом, которые принимают фиксированное число аргументов и работают в контексте включающей их области видимости, то есть — в контексте функции или другого кода, в котором они объявлены.
Поговорим об этом подробнее.
▍Синтаксис стрелочных функций
Стрелочные функции строятся по единой схеме, при этом структура функций может быть, в особых случаях, упрощена. Базовая структура стрелочной функции выглядит так:
Список аргументов функции находится в круглых скобках, после него следует стрелка, составленная из символов = и > , а дальше идёт тело функции в фигурных скобках.
Это очень похоже на то, как устроены обычные функции, главные отличия заключаются в том, что здесь опущено ключевое слово function и добавлена стрелка после списка аргументов.
В определённых случаях, однако, простые стрелочные функции можно объявлять, используя гораздо более компактные конструкции.
Рассмотрим вариант синтаксиса, который используется в том случае, если тело функции представлено единственным выражением. Он позволяет обойтись без фигурных скобок, обрамляющих тело функции, и избавляет от необходимости явного возврата результатов вычисления выражения, так как этот результат будет возвращён автоматически. Например, это может выглядеть так:
Вот ещё один вариант сокращённой записи функции, применяемый в том случае, когда функция имеет лишь один аргумент.
Как видите, тут опущены круглые скобки, обрамляющие список аргументов. Кроме того, тело функции, которое и в этом примере представлено одной командой, так же записано без скобок. Позже мы ещё поговорим о преимуществах подобных конструкций.
▍Возврат объектов и сокращённая запись стрелочных функций
При работе со стрелочными функциями используются и некоторые более сложные синтаксические конструкции, о которых полезно знать.
Например, попытаемся использовать однострочное выражение для возврата из функции объектного литерала. Может показаться, учитывая то, что мы уже знаем о стрелочных функциях, что объявление функции будет выглядеть так:
Проблема этого кода заключается в его неоднозначности. А именно, фигурные скобки, которые мы хотим использовать для описания объектного литерала, выглядят так, будто мы пытаемся заключить в них тело функции.
Для того чтобы указать системе на то, что мы имеем в виду именно объектный литерал, нужно заключить его в круглые скобки:
▍Стрелочные функции и включающий их контекст выполнения
В отличие от других функций, стрелочные функции не имеют собственного контекста выполнения.
На практике это означает, что они наследуют сущности this и arguments от родительской функции.
Например, сравним две функции, представленные в следующем коде. Одна и них обычная, вторая — стрелочная.
Тут имеется объект test с двумя методами. Каждый из них представляет собой функцию, которая создаёт и возвращает анонимную функцию. Разница между этими методами заключается лишь в том, что в первом из них используется традиционное функциональное выражение, а во втором — стрелочная функция.
Если поэкспериментировать с этим кодом в консоли, передавая методам объекта одни и те же аргументы, то, хотя методы и выглядят очень похожими, мы получим разные результаты:
У анонимной функции есть собственный контекст, поэтому, когда её вызывают, при обращении к test.name не будет выдано значение свойства name объекта, а при обращении к arguments не будет выведен список аргументов функции, которая использовалась для создания и возврата исследуемой функции.
В случае же со стрелочной функцией оказывается, что её контекст совпадает с контекстом создавшей её функции, что даёт ей доступ и к списку аргументов, переданных этой функцией, и к свойству name объекта, методом которого эта функция является.
Ситуации, в которых стрелочные функции улучшают код
▍Обработка списков значений
Традиционными лямбда-функциями, а также — стрелочными функциями, после появления их в JavaScript, обычно пользуются в ситуации, когда некая функция применяется к каждому элементу некоего списка.
Например, если имеется массив значений, который нужно преобразовать с использованием метода массивов map , для описания такого преобразования идеально подходит стрелочная функция:
Вот чрезвычайно распространённый пример подобного использования стрелочных функций, который заключается в работе со свойствами объектов:
Аналогично, если вместо традиционных циклов for используют современные циклы forEach , основанные на итераторах, то, что стрелочные функции используют this родительской сущности, делает их использование понятным на интуитивном уровне:
▍Промисы и цепочки промисов
Ещё одна ситуация, где стрелочные функции позволяют писать более чистый и понятный код, представлена асинхронными программными конструкциями.
Так, промисы значительно упрощают работу с асинхронным кодом. При этом, даже если вы предпочитаете использовать конструкцию async/await, то без понимания промисов вам не обойтись, так как эта конструкция основана на них.
Однако при использовании промисов нужно объявлять функции, которые вызываются после завершения работы асинхронного кода или завершения асинхронного обращения к некоему API.
Это — идеальное место для использования стрелочных функций, в особенности в том случае, если результирующая функция обладает неким состоянием, ссылается на что-либо в объекте. Например, это может выглядеть так:
▍Трансформация объектов
Ещё один распространённый пример использования стрелочных функций заключается в инкапсуляции трансформаций объектов.
Например, в Vue.js существует общепринятый паттерн включения фрагментов хранилища Vuex напрямую в компонент Vue с использованием mapState .
Эта операция включает в себя объявления набора «преобразователей», которые выбирают из исходного полного состояния именно то, что нужно для конкретного компонента.
Такие вот простые преобразования — идеальное место для использования стрелочных функций. Например:
Ситуации, в которых не следует использовать стрелочные функции
▍Методы объектов
Существует целый ряд ситуаций, в которых использование стрелочных функций — не лучшая идея. Стрелочные функции, если применять их необдуманно, не только не помогают программистам, но и становятся источником проблем.
Первая такая ситуация заключается в использовании стрелочных функций в качестве методов объектов. Здесь важны контекст выполнения и ключевое слово this , характерные для традиционных функций.
Одно время популярным было применение комбинации свойств классов и стрелочных функций для создания методов с «автоматической привязкой», то есть таких, которые могут быть использованы обработчиками событий, но остаются привязанными к классу. Выглядело это примерно так:
При использовании подобной конструкции, даже если функция handleClick вызывалась обработчиком событий, а не в контексте экземпляра класса Counter , у этой функции был доступ к данным этого экземпляра.
Однако у такого подхода масса минусов, которым посвящён этот материал.
Хотя применение стрелочной функции здесь, безусловно, представляет собой удобно выглядящий способ привязки функции, поведение этой функции во многих аспектах оказывается далёким от интуитивной понятности, мешая тестированию и создавая проблемы в ситуациях, когда, например, соответствующий объект пытаются использовать как прототип.
В подобных случаях, вместо стрелочных функций, используйте обычные функции, и, если нужно, привязывайте к ним экземпляр объекта в конструкторе:
▍Длинные цепочки вызовов
Стрелочные функции могут стать источником проблем в том случае, если их планируется использовать во множестве различных комбинаций, в частности — в длинных цепочках вызовов функций.
Основная причина подобных проблем, как и при использовании анонимных функций, заключается в том, что они дают крайне неинформативные результаты трассировки стека вызовов.
▍Функции с динамическим контекстом
Последняя из обсуждаемых нами ситуаций, в которых стрелочные функции могут стать источником неприятностей, заключается в использовании их там, где нужна динамическая привязка this .
Если в подобных ситуациях применяются стрелочные функции, то динамическая привязка this работать не будет. Эта неприятная неожиданность может заставить поломать голову над причинами происходящего тех, кому придётся работать кодом, в котором стрелочными функциями пользуются неправильно.
Вот некоторые вещи, о которых нужно помнить, рассматривая возможность использования стрелочных функций:
- Обработчики событий вызываются с this , привязанным к атрибуту события currentTarget .
- Если вы всё ещё пользуетесь jQuery, учитывайте, что большинство методов jQuery привязывают this к выбранному элементу DOM.
- Если вы пользуетесь Vue.js, то методы и вычисляемые функции обычно привязывают this к компоненту Vue.
Итоги
Стрелочные функции — это замечательная свежая возможность JavaScript. Они позволяют, во многих ситуациях, писать более удобный код, чем раньше. Но, как и в случае с другими возможностями, у них есть и преимущества, и недостатки. Поэтому использовать стрелочные функции надо там, где они могут принести пользу, не рассматривая их в виде полной замены для обычных функций.
Выражения стрелочных функций имеют более короткий синтаксис по сравнению с функциональными выражениями и лексически привязаны к значению this (но не привязаны к собственному this, arguments, super, или new.target). Выражение стрелочных функций не позволяют задавать имя, поэтому стрелочные функции анонимны, если их ни к чему не присвоить.
Синтаксис
Базовый синтаксис
Расширенный синтаксис
Подробные примеры синтаксиса можно посмотреть здесь.
Описание
Два фактора повлияли на появление стрелочных функции: более короткий синтаксис и лексика this .
Короткие функции
В некоторых функциональных шаблонах приветствуются более короткие функции. Сравните:
Отсутствие связывания с this
До появления стрелочных функций, каждая новая функция имела своё значение this (новый объект в случае конструктора, undefined в strict режиме вызова функции, контекст объекта при вызове функции как "метода объекта" и т.д.). Это очень раздражало при использовании объектно-ориентированного стиля программирования.
В ECMAScript 3/5, данная проблема решалась присваиванием значения this переменной:
Кроме этого, может быть создана привязанная функция, в которую передаётся требуемое значение this для функции (функция growUp() в примере выше).
Стрелочные функции не содержат собственный контекст this , а используют значение this окружающего контекста. Поэтому нижеприведённый код работает как предполагалось:
Строгий режим исполнения
Поскольку значение this определяется лексикой, правила строгого режима (strict mode) относительно this игнорируются:
Все остальные правила строгого режима применяются как обычно.
Вызов с помощью call или apply
Так как значение this определяется лексикой, вызов стрелочных функций с помощью методов call() или apply() , даже если передать аргументы в эти методы, не влияет на значение this :
Не имеет собственного объекта arguments
Стрелочные функции не имеют собственного объекта arguments, поэтому в теле стрелочных функций arguments будет ссылаться на переменную в окружающей области.
В большинстве случаев лучшей заменой объекта arguments в стрелочных функциях являются rest параметры:
Использование стрелочных функций как методов
Как показано ранее, стрелочные функции лучше всего подходят для функций без методов. Посмотрим, что будет, когда мы попробуем их использовать как методы:
Стрелочные функции не объявляют привязку ("bind") их контекста this . Другой пример включает Object.defineProperty() :
Использование оператора new
Стрелочные функции не могут быть использованы как конструктор и вызовут ошибку при использовании с new :
Использование ключевого слова yield
Ключевое слово yield не может быть использовано в теле стрелочной функции (за исключением случаев, когда разрешается использовать в функциях, вложенных в тело стрелочной функции). Как следствие стрелочные функции не могут быть использованы как генераторы.
Тело функции
Тело стрелочной функции может иметь краткую (concise body) или блочную (block body) форму.
Блочная форма не возвращает значение, необходимо явно вернуть значение.
Возвращаемые объектные строки (литералы)
Помните о том, что возвращаемые объектные строки используют сокращённый синтаксис: params =>
Это происходит потому что код в скобках (<>) распознаётся как цепочка выражений (т.е. foo трактуется как наименование, а не как ключ в объектной строке).
Не забывайте оборачивать скобками объектные строки.
Разрывы строк
Стрелочная функция не может содержать разрывы строк между параметрами и стрелкой.
Разбор порядка следования
Поскольку стрелка в стрелочной функции не является оператором, то стрелочные функции имеют специальные правила разбора (парсинга), которые взаимодействуют с приоритетами операторов иначе, чем в обычных функциях.
Читайте также: