Лямбда функции в javascript

Добавил пользователь Дмитрий К.
Обновлено: 19.09.2024

Существует ещё один очень простой и лаконичный синтаксис для создания функций, который часто лучше, чем Function Expression.

Это создаёт функцию func , которая принимает аргументы arg1..argN , затем вычисляет expression в правой части с их использованием и возвращает результат.

Другими словами, это сокращённая версия:

Давайте рассмотрим конкретный пример:

Как вы можете видеть, (a, b) => a + b задаёт функцию, которая принимает два аргумента с именами a и b . И при выполнении она вычисляет выражение a + b и возвращает результат.

Если у нас только один аргумент, то круглые скобки вокруг параметров можно опустить, сделав запись ещё короче:

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

Стрелочные функции можно использовать так же, как и Function Expression.

Например, для динамического создания функции:

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

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

Многострочные стрелочные функции

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

Иногда нам нужна более сложная функция, с несколькими выражениями и инструкциями. Это также возможно, нужно лишь заключить их в фигурные скобки. При этом важное отличие – в том, что в таких скобках для возврата значения нужно использовать return (как в обычных функциях).

Здесь мы представили главной целью стрелочных функций краткость. Но это ещё не всё!

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

Чтобы изучить их более подробно, нам сначала нужно познакомиться с некоторыми другими аспектами JavaScript, поэтому мы вернёмся к стрелочным функциям позже, в главе Повторяем стрелочные функции.

А пока мы можем использовать их для простых однострочных действий и колбэков.

Итого

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

An arrow function expression is a compact alternative to a traditional function expression, but is limited and can't be used in all situations.

There are differences between arrow functions and traditional functions, as well as some limitations:

  • Arrow functions don't have their own bindings to this or super , and should not be used as methods .
  • Arrow functions don't have access to the new.target keyword.
  • Arrow functions aren't suitable for call , apply and bind methods, which generally rely on establishing a scope.
  • Arrow functions cannot be used as constructors.
  • Arrow functions cannot use yield , within its body.

Try it

Comparing traditional functions to arrow functions

Let's decompose a "traditional anonymous function" down to the simplest "arrow function" step-by-step:

Note: Each step along the way is a valid "arrow function".

The < braces >and ( parentheses ) and "return" are required in some cases.

For example, if you have multiple arguments or no arguments, you'll need to re-introduce parentheses around the arguments:

Likewise, if the body requires additional lines of processing, you'll need to re-introduce braces PLUS the "return" (arrow functions do not magically guess what or when you want to "return"):

And finally, for named functions we treat arrow expressions like variables:

Syntax

Basic syntax

One param. With simple expression return is not needed:

Multiple params require parentheses. With simple expression return is not needed:

Multiline statements require body braces and return:

Multiple params require parentheses. Multiline statements require body braces and return:

Advanced syntax

To return an object literal expression requires parentheses around expression:

Destructuring within params supported:

Description

Arrow functions used as methods

As stated previously, arrow function expressions are best suited for non-method functions. Let's see what happens when we try to use them as methods:

Arrow functions do not have their own this . Another example involving Object.defineProperty() :

call, apply and bind

The call , apply and bind methods are NOT suitable as arrow functions – as they were designed to allow methods to execute within different scopes – because arrow functions establish this based on the scope the arrow function is defined within.

For example call , apply and bind work as expected with traditional functions, because we establish the scope for each of the methods:

With Arrow functions, since our add function is essentially created on the window (global) scope, it will assume this is the window.

Perhaps the greatest benefit of using Arrow functions is with DOM-level methods ( setTimeout , setInterval , addEventListener ) that usually required some kind of closure, call, apply or bind to ensure the function executed in the proper scope.

Traditional function example

Arrow function example

No binding of arguments

Arrow functions do not have their own arguments object. Thus, in this example, arguments is a reference to the arguments of the enclosing scope:

In most cases, using rest parameters is a good alternative to using an arguments object.

Use of the new operator

Arrow functions cannot be used as constructors and will throw an error when used with new .

Use of prototype property

Arrow functions do not have a prototype property.

Use of the yield keyword

The yield keyword may not be used in an arrow function's body (except when permitted within functions further nested within it). As a consequence, arrow functions cannot be used as generators.

Function body

Arrow functions can have either a concise body or the usual block body.

In a concise body, only an expression is specified, which becomes the implicit return value. In a block body, you must use an explicit return statement.

Returning object literals

Keep in mind that returning object literals using the concise body syntax params => will not work as expected.

This is because the code inside braces (<>) is parsed as a sequence of statements (i.e. foo is treated like a label, not a key in an object literal).

You must wrap the object literal in parentheses:

Line breaks

An arrow function cannot contain a line break between its parameters and its arrow.

However, this can be amended by putting the line break after the arrow or using parentheses/braces as seen below to ensure that the code stays pretty and fluffy. You can also put line breaks between arguments.

Parsing order

Although the arrow in an arrow function is not an operator, arrow functions have special parsing rules that interact differently with operator precedence compared to regular functions.

Выражения стрелочных функций имеют более короткий синтаксис по сравнению с функциональными выражениями и лексически привязаны к значению 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 в стрелочных функциях являются остаточные параметры:

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

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

Стрелочные функции не объявляют привязку ("bind") их контекста this . Другой пример включает Object.defineProperty() :

Использование оператора new

Стрелочные функции не могут быть использованы как конструктор и вызовут ошибку при использовании с new :

Использование ключевого слова yield

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

Тело функции

Тело стрелочной функции может иметь краткую (concise body) или блочную (block body) форму.

Блочная форма не возвращает значение, необходимо явно вернуть значение.

Возвращаемые объектные строки (литералы)

Помните о том, что возвращаемые объектные строки используют сокращённый синтаксис: params => будет работать не так, как ожидается.

Это происходит потому что код в скобках (<>) распознаётся как цепочка выражений (т.е. foo трактуется как наименование, а не как ключ в объектной строке).

Не забывайте оборачивать скобками объектные строки.

Разрывы строк

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

Разбор порядка следования

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

Функции - ключевая концепция в JavaScript. Важнейшей особенностью языка является первоклассная поддержка функций​ (functions as first-class citizen). Любая функция это объект, и следовательно ею можно манипулировать как объектом, в частности:

  • передавать как аргумент и возвращать в качестве результата при вызове других функций (функций высшего порядка);
  • создавать анонимно и присваивать в качестве значений переменных или свойств объектов.

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

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

Для понимания работы функций необходимо (и достаточно?) иметь представление о следующих моментах:

  • способы объявления
  • способы вызова
  • параметры и аргументы вызова ( arguments )
  • область данных ( Scope ) и замыкания ( Closures )
  • объект привязки ( this )
  • возвращаемое значение ( return )
  • исключения ( throw )
  • использование в качестве конструктора объектов
  • сборщик мусора ( garbage collector )

Объявление функций

Функции вида "function declaration statement"

Объявление функции (function definition, или function declaration, или function statement) состоит из ключевого слова function и следующих частей:

  • Имя функции.
  • Список параметров (принимаемых функцией) заключённых в круглые скобки () и разделённых запятыми.
  • Инструкции, которые будут выполнены после вызова функции, заключают в фигурные скобки < >.

Функция square принимает один параметр, названный number. Состоит из одной инструкции, которая означает вернуть параметр этой функции (это number ) умноженный на самого себя. Инструкция return указывает на значение, которые будет возвращено функцией.

Примитивные параметры (например, число) передаются функции значением; значение передаётся в функцию, но если функция меняет значение параметра, это изменение не отразится глобально или после вызова функции.

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

Функции вида "function definition expression"

Функция вида "function declaration statement" по синтаксису является инструкцией (statement), ещё функция может быть вида "function definition expression". Такая функция может быть анонимной (она не имеет имени). Например, функция square может быть вызвана так:

Однако, имя может быть и присвоено для вызова самой себя внутри самой функции и для отладчика (debugger) для идентифицированные функции в стек-треках (stack traces; "trace" — "след" / "отпечаток").

Функции вида "function definition expression" удобны, когда функция передаётся аргументом другой функции. Следующий пример показывает функцию map , которая должна получить функцию первым аргументом и массив вторым.

В следующим коде наша функция принимает функцию, которая является function definition expression, и выполняет его для каждого элемента принятого массива вторым аргументом.

Функция возвращает: [0, 1, 8, 125, 1000].

В JavaScript функция может быть объявлена с условием. Например, следующая функция будет присвоена переменной myFunc только, если num равно 0:

В дополнение к объявлениям функций, описанных здесь, вы также можете использовать конструктор Function для создания функций из строки во время выполнения (runtime), подобно eval() .

Метод — это функция, которая является свойством объекта. Узнать больше про объекты и методы можно по ссылке: Работа с объектами.

Вызовы функций

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

Вызов функции фактически выполняет указанные действия с указанными параметрами. Например, если вы определите функцию square , вы можете вызвать её следующим образом:

Эта инструкция вызывает функцию с аргументом 5. Функция вызывает свои инструкции и возвращает значение 25.

Функции могут быть в области видимости, когда они уже определены, но функции вида "function declaration statment" могут быть подняты (поднятие — hoisting), также как в этом примере:

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

Примечание: Это работает только тогда, когда объявлении функции использует вышеупомянутый синтаксис (т.е. function funcName()<> ). Код ниже не будет работать. Имеется в виду то, что поднятие функции работает только с function declaration и не работает с function expression.

Аргументы функции не ограничиваются строками и числами. Вы можете передавать целые объекты в функцию. Функция show_props() (объявленная в Работа с объектами) является примером функции, принимающей объекты аргументом.

Функция может вызвать саму себя. Например, вот функция рекурсивного вычисления факториала:

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

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

Область видимости функций

Переменные объявленные в функции не могут быть доступными где-нибудь вне этой функции, поэтому переменные (которые нужны именно для функции) объявляют только в scope функции. При этом функция имеет доступ ко всем переменным и функциям, объявленным внутри её scope. Другими словами функция объявленная в глобальном scope имеет доступ ко всем переменным в глобальном scope. Функция объявленная внутри другой функции ещё имеет доступ и ко всем переменным её родительской функции и другим переменным, к которым эта родительская функция имеет доступ.

Scope и стек функции

Рекурсия

Функция может вызывать саму себя. Три способа такого вызова:

  1. по имени функции
  2. arguments.callee
  3. по переменной, которая ссылается на функцию

Для примера рассмотрим следующие функцию:

Внутри функции (function body) все следующие вызовы эквивалентны:

Функция, которая вызывает саму себя, называется рекурсивной функцией (recursive function). Получается, что рекурсия аналогична циклу (loop). Оба вызывают некоторый код несколько раз, и оба требуют условия (чтобы избежать бесконечного цикла, вернее бесконечной рекурсии). Например, следующий цикл:

можно было изменить на рекурсивную функцию и вызовом этой функции:

Однако некоторые алгоритмы не могут быть простыми повторяющимися циклами. Например, получение всех элементов структуры дерева (например, DOM) проще всего реализуется использованием рекурсии:

В сравнении с функцией loop , каждый рекурсивный вызов сам вызывает много рекурсивных вызовов.

Также возможно превращение некоторых рекурсивных алгоритмов в нерекурсивные, но часто их логика очень сложна, и для этого потребуется использование стека (stack). По факту рекурсия использует stack: function stack.

Поведение стека можно увидеть в следующем примере:

Вложенные функции (nested functions) и замыкания (closures)

Вы можете вложить одну функцию в другую. Вложенная функция (nested function; inner) приватная (private) и она помещена в другую функцию (outer). Так образуется замыкание (closure). Closure — это выражение (обычно функция), которое может иметь свободные переменные вместе со средой, которая связывает эти переменные (что "закрывает" ("close") выражение).

Поскольку вложенная функция это closure, это означает, что вложенная функция может "унаследовать" (inherit) аргументы и переменные функции, в которую та вложена. Другими словами, вложенная функция содержит scope внешней ("outer") функции.

Следующий пример показывает вложенную функцию:

Поскольку вложенная функция формирует closure, вы можете вызвать внешнюю функцию и указать аргументы для обоих функций (для outer и innner).

Сохранение переменных

Обратите внимание, значение x сохранилось, когда возвращалось inside . Closure должно сохранять аргументы и переменные во всем scope. Поскольку каждый вызов предоставляет потенциально разные аргументы, создаётся новый closure для каждого вызова во вне. Память может быть очищена только тогда, когда inside уже возвратился и больше не доступен.

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

Несколько уровней вложенности функций (Multiply-nested functions)

Функции можно вкладывать несколько раз, т.е. функция (A) хранит в себе функцию (B), которая хранит в себе функцию (C). Обе функции B и C формируют closures, так B имеет доступ к переменным и аргументам A, и C имеет такой же доступ к B. В добавок, поскольку C имеет такой доступ к B, который имеет такой же доступ к A, C ещё имеет такой же доступ к A. Таким образом closures может хранить в себе несколько scope; они рекурсивно хранят scope функций, содержащих его. Это называется chaining (chain — цепь; Почему названо "chaining" будет объяснено позже)

Рассмотрим следующий пример:

В этом примере C имеет доступ к y функции B и к x функции A . Так получается, потому что:

  1. Функция B формирует closure, включающее A , т.е. B имеет доступ к аргументам и переменным функции A .
  2. Функция C формирует closure, включающее B .
  3. Раз closure функции B включает A , то closure С тоже включает A, C имеет доступ к аргументам и переменным обоих функций B и A . Другими словами, С связывает цепью (chain) scopes функций B и A в таком порядке.

В обратном порядке, однако, это не верно. A не имеет доступ к переменным и аргументам C , потому что A не имеет такой доступ к B . Таким образом, C остаётся приватным только для B .

Конфликты имён (Name conflicts)

Когда два аргумента или переменных в scope у closure имеют одинаковые имена, происходит конфликт имени (name conflict). Более вложенный (more inner) scope имеет приоритет, так самый вложенный scope имеет наивысший приоритет, и наоборот. Это цепочка областей видимости (scope chain). Самым первым звеном является самый глубокий scope, и наоборот. Рассмотрим следующие:

Конфликт имени произошёл в инструкции return x * 2 между параметром x функции inside и переменной x функции outside . Scope chain здесь будет таким: < inside ==>outside ==> глобальный объект (global object)>. Следовательно x функции inside имеет больший приоритет по сравнению с outside , и нам вернулось 20 (= 10 * 2), а не 10 (= 5 * 2).

Замыкания

Closures это один из главных особенностей JavaScript. JavaScript разрешает вложенность функций и предоставляет вложенной функции полный доступ ко всем переменным и функциям, объявленным внутри внешней функции (и другим переменным и функции, к которым имеет доступ эта внешняя функция).

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

Также, поскольку вложенная функция имеет доступ к scope внешней функции, переменные и функции, объявленные во внешней функции, будет продолжать существовать и после её выполнения для вложенной функции, если на них и на неё сохранился доступ (имеется ввиду, что переменные, объявленные во внешней функции, сохраняются, только если внутренняя функция обращается к ним).

Closure создаётся, когда вложенная функция как-то стала доступной в неком scope вне внешней функции.

Более сложный пример представлен ниже. Объект с методами для манипуляции вложенной функции внешней функцией можно вернуть (return).

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

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

Использование объекта arguments

Объект arguments функции является псевдо-массивом. Внутри функции вы можете ссылаться к аргументам следующим образом:

где i — это порядковый номер аргумента, отсчитывающийся с 0. К первому аргументу, переданному функции, обращаются так arguments[0] . А получить количество всех аргументов — arguments.length .

С помощью объекта arguments Вы можете вызвать функцию, передавая в неё больше аргументов, чем формально объявили принять. Это очень полезно, если вы не знаете точно, сколько аргументов должна принять ваша функция. Вы можете использовать arguments.length для определения количества аргументов, переданных функции, а затем получить доступ к каждому аргументу, используя объект arguments .

Для примера рассмотрим функцию, которая конкатенирует несколько строк. Единственным формальным аргументом для функции будет строка, которая указывает символы, которые разделяют элементы для конкатенации. Функция определяется следующим образом:

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

Примечание: arguments является псевдо-массивом, но не массивом. Это псевдо-массив, в котором есть пронумерованные индексы и свойство length . Однако он не обладает всеми методами массивов.

Рассмотрите объект Function в JavaScript-справочнике для большей информации.

Параметры функции

Начиная с ECMAScript 2015 появились два новых вида параметров: параметры по умолчанию (default parameters) и остаточные параметры (rest parameters).

Параметры по умолчанию (Default parameters)

В JavaScript параметры функции по умолчанию имеют значение undefined . Однако в некоторых ситуация может быть полезным поменять значение по умолчанию. В таких случаях default parameters могут быть весьма кстати.

В прошлом для этого было необходимо в теле функции проверять значения параметров на undefined и в положительном случае менять это значение на дефолтное (default). В следующем примере в случае, если при вызове не предоставили значение для b , то этим значением станет undefined , тогда результатом вычисления a * b в функции multiply будет NaN . Однако во второй строке мы поймаем это значение:

С параметрами по умолчанию проверка наличия значения параметра в теле функции не нужна. Теперь вы можете просто указать значение по умолчанию для параметра b в объявлении функции:

Для более детального рассмотрения ознакомьтесь с параметрами по умолчанию.

Остаточные параметры (Rest parameters)

Остаточные параметры предоставляют нам массив неопределённых аргументов. В примере мы используем остаточные параметры, чтобы собрать аргументы с индексами со 2-го до последнего. Затем мы умножим каждый из них на значение первого аргумента. В этом примере используется стрелочная функция (Arrow functions), о которой будет рассказано в следующей секции.

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

Стрелочные функции — функции вида "arrow function expression" (неверно fat arrow function) — имеют укороченный синтаксис по сравнению с function expression и лексически связывает значение this . Стрелочные функции всегда анонимны. Посмотрите также пост блога hacks.mozilla.org "ES6 In Depth: Arrow functions".

На введение стрелочных функций повлияли два фактора: более короткие функции и лексика this .

Более короткие функции

В некоторых функциональных паттернах приветствуется использование более коротких функций. Сравните:

Лексика this

До стрелочных функций каждая новая функция определяла своё значение this (новый объект в случае конструктора, undefined в strict mode, контекстный объект, если функция вызвана как метод объекта, и т.д.). Это оказалось раздражающим с точки зрения объектно-ориентированного стиля программирования.

В ECMAScript 3/5 эта проблема была исправлена путём присвоения значения this переменной, которую можно было бы замкнуть.

Альтернативой может быть связанная функция (bound function), с которой можно правильно вручную определить значение this для функции growUp() .

В arrow function значением this является окружающий его контекст, так следующий код работает ожидаемо:

Далее

Подробное техническое описание функций в статье справочника Функции

Смотрите также Function в Справочнике JavaScript для получения дополнительной информации по функции как объекту.

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

image


Иногда эти и другие преимущества ведут к тому, что стрелочному синтаксису отдают безусловное предпочтение перед другими способами объявления функций. Например, популярная конфигурации 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. Они позволяют, во многих ситуациях, писать более удобный код, чем раньше. Но, как и в случае с другими возможностями, у них есть и преимущества, и недостатки. Поэтому использовать стрелочные функции надо там, где они могут принести пользу, не рассматривая их в виде полной замены для обычных функций.

Читайте также: