Php стрелочные функции. ES6: Стрелочные функции (XI). Как насчет аргументов

  • Перевод

“Толстые” стрелочные функции (=>), так же известные, как 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; }) .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) .then(verifyUser) .then((user, verifiedToken) => verifiedToken) .catch(err => log(err.stack)); }
Здесь надо обратить внимание на несколько важных моментов:
  • Мы потеряли function и {}, потому что наши callback функции записываются в одну строку.
  • Мы убрали (). Теперь они не обертывают список аргументов, когда присутствует только один аргумент (остальные аргументы проходят как исключения; например, (...args) => ...).
  • Мы избавились от ключевого слова return. Убирая {}, мы позволяем однострочным стрелочным функциям провести неявный возврат (в других языках такие функции часто называют лямбда функциями).
Еще раз обратим внимание на последний пункт. Неявный возврат происходит только в случае с однострочными стрелочными функциями. Когда стрелочная функция определяется с {}, даже если она является отдельным оператором, неявный возврат не происходит.
const getVerifiedToken = selector => { return getUsers() .then(users => users) .then(verifyUser) .then((user, verifiedToken) => verifiedToken) .catch(err => log(err.stack)); }
Здесь начинается самое интересное. Так как у нашей функции есть только один оператор, мы можем убрать {}, и код будет очень похож на синтаксис CoffeeScript :
const getVerifiedToken = selector => getUsers() .then(users => users) .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; } (...args) => args () => {} // 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); print(`LOG: ${msg}`); } log("hello"); // hello
Повторимся, что у стрелочных функций нет this и нет arguments. Однако, приняв это во внимание, вы все же можете получить аргументы, переданные в стрелочные функции с помощью rest-параметров (так же известны, как spread операторы):
function log(msg) { const print = (...args) => console.log(args); print(`LOG: ${msg}`); } log("hello"); // LOG: hello Как насчет генераторов? “Толстые” стрелочные функции не могут использоваться как генераторы. Никаких исключений и обходных путей нет. Точка.Вывод “Толстые” стрелочные функции – одна из причин, почему я так люблю JavaScript. Очень соблазнительно просто начать использовать => вместо function. Я видел целые библиотеки, где используется только вариант =>. Не думаю, однако, что это разумно. В конце концов, у => есть много особенностей и скрытых функций. Я рекомендую использовать стрелочные функции только там, где вам нужна новая функциональность:
  • Функции с одиночными операторами, которые сразу же делают возврат;
  • функции, которые должны работать с this с родительским scope.
ES6 сегодня Так можно ли воспользоваться возможностями ES6 уже сегодня? Использование транспайлеров стало нормой в последние несколько лет. Ни простые разработчики, ни крупные компании не стесняются их применять.

Последнее обновление: 09.04.2018

Стрелочные функции (arrow functions) представляют сокращенную версию обычных функций. Стрелочные функции образуются с помощью знака стрелки (=>), перед которым в скобках идут параметры функции, а после - собственно тело функции. Например:

Let sum = (x, y) => x + y; let a = sum(4, 5); // 9 let b = sum(10, 5); // 15

В данном случае функция (x, y) => x + y осуществляет сложение двух чисел и присваивается переменной sum. Функция принимает два параметра - x и y. Ее тело составляет сложение значений этих параметров. И поскольку после стрелки фактически идет конкретное значение, которое представляет сумму чисел, то функция возвращает это значение. И мы можем через переменную sum вызвать данную функцию и получить ее результат в переменные a и b.

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

Let sum = (x, y) => console.log(x + y); sum(4, 5); // 9 sum(10, 5); // 15

В данном случае функция console.log() ничего не возвращает, и соответственно функция sum также не возвращает никакого результата.

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

Var square = n => n * n; console.log(square(5)); // 25 console.log(square(6)); // 36 console.log(square(-7)); // 49

Если тело функции представляет набор выражений, то они облекаются в фигурные скобки:

Var square = n => { let result = n * n; return result; } console.log(square(5)); // 25

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

Особо следует остановиться на случае, когда стрелочная функция возвращает объект:

Let user = (userName, userAge) => ({name: userName, age: userAge}); let tom = user("Tom", 34); let bob = user("Bob", 25); console.log(tom.name, tom.age); // "Tom", 34 console.log(bob.name, bob.age); // "Bob", 25

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

Если стрелочная функция не принимает никаких параметров, то ставятся пустые скобки:

Var hello = ()=> console.log("Hello World"); hello(); // Hello World hello(); // Hello World

В ES6 есть новый способ создания функций - С помощью оператора Стрелка => . Такие функции называются стрелочные. Они предлагают более компактный синтаксис. Они не имеют имени и они по-своему работают с this .

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

Откроем папку проекта в командной строке (КС). Вводим команду:

И нажать Enter

В папке src создадим файл arr.js и сразу укажу его в файле index.html

</script>

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

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

Function add (x, y) { return x + y; } console.log (add (3, 6));

В консоли мы увидим результат - 9

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

Уберем слово function , уберем имя функции и уберем фигурные скобки, и слово - return . После параметров поставим стрелку.

Let add = (x, y) => x + y; console.log (add (4, 6));

Если посмтреть на тип переменной add используя оператор typeof:

Console.log(typeof(add));

То мы увидим в консоли function

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

"use strict"; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var add = function add(x, y) { return x + y; }; console.log(add(4, 6)); console.log(typeof add === "undefined" ? "undefined" : _typeof(add));

Мы видим, что Babel превратил наш код в простое выражение функцией.

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

Let add = (x, y) => x + y; console.log (add (4, 6)); console.log(typeof(add)); let square = function(a) { return a * a; } console.log(square (4));

Посмотрим в консоли:

Стрелочная функция будет выглядеть вот так:

Let square = x => x * x;

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

Давайте напишем функцию которая вообще не принимает параметров.

Function givNumer () { return 33; } console.log(givNumer ());

Эта функция просто выводит в консоль число 33. Стрелочная:

Let givNumer = () => 33; console.log(givNumer ());

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

Let log = function () { console.log("Hello World!"); }; log();

Стрелочная:

Let log = () => console.log("Hello World!!!"); log();

Создадим функцию тело которой будет состоять из двух строк.

Функция будет принимать два параметра. Теле функции создадим переменную. После этого вернём результат.

Let mult = function (a, b) { let result = a * b; return result; } console.log(mult (4, 5));

Если в стрелочной функции несколько строк, то фигурные скобки - {} обязательны! И обязательно определить то, что возвращает эта функция, используя ключевое слово return

Стрелочная:

Let mult = (a, b) => { let result = a * b; return result; } console.log(mult (4, 5));

Теперь создадим функцию, которая возвращает литерал объекта:

Let literal = function () { return { name: "John"}; } console.log (literal ());

В консоли мы увидим:

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

Следует помнить, что если стрелочная функция возвращает литерал объекта, то нужны круглые скобки - ()

Стрелочная функция возвращающая литерал объекта:

Let literal = () => ({ name: "John"}); console.log (literal ());

Теперь попробуем использовать стрелочную функцию в качестве IIFE - Immediately-invoked function expression

Если сказать коротко, то это функция, которая выполняется сразу после объявления

Это выглядит вот так:

(function () { console.log("IIFE"); })();

Стрелочная IIFE - функция будет выглядеть вот так:

(() => console.log("IIFE"))();

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

Её нельзя просто так взять и спустить на строку ниже. Выдаст ошибку!

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

Давайте создадим массив с некоторыми числами и назовем его numbers . Я думаю что вы знаете, что у массивов есть полезные методы, которые позволяют это массив перебирать, фильтровать и т.д.

Давайте вы считаем сумму всех переменных массива. Для этого я объявлю еще одну переменную - let sum = 0;

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

Let numbers = ; let sum = 0; numbers.forEach(function(num) { sum += num; }); console.log (sum);

В консоли мы увидим 55 . Давайте превратим эту функцию в стрелочную: numbers.forEach(num => sum += num); console.log(sum);

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

Также мы можем возвести в квадрат каждый элемент массива.

Let squared = numbers.map(n => n * n); console.log (squared);

Стрелочные функции и this . Для этого я создам литерал объекта который сохраню в переменную рerson .

У объекта person будет свойство name со значением ‘Bob’ и свойства greet - поприветствовать.В консоль выведем приветствие также посмотрим назначение this .

Let person = { name: "Bob", greet: function () { console.log("Hello! My name is " + this.name); console.log(this); } }; person.greet();

В консоли браузера мы увидим приветствие и сам объект person .

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

Let person = { name: "Bob", greet: () => { console.log("Hello! My name is " + this.name); console.log(this); } }; person.greet();

Теперь мы не получили значение имени и в качестве значения this - window !

Но почему? Дело в том, что значение this берется из контекста в котором функция объявлена. ! Независимо от того, где эта функция будет выполнена. Это можно увидеть на картинке:

У нас есть программа.

В ней пока что кроме объекта window ничего нет. Добавили объект person. Заметьте, что у метода мы используем стрелочную функцию. Как мы и говорили - значение this будет браться из контекста. Контекст это окружение. В данном случае окружением объекта person , всех его свойств и методов, будет являться объект window . И если значение this будет браться из контекста, то this будет ссылаться на объект window .

Если мы рассмотрим обычную функцию, то мы знаем, что this ссылается на сам объект person . Вы можете спросить, почему значение this в стрелочных функциях берется из контекста? А ответ очень простой - их так сделали! :-) Дело в том, что стрелочные функции были созданы для решения проблем в другой ситуации. Давайте посмотрим на примере. Для того, чтобы увидеть проблему мы вернемся к нашей стрелочной функции.

Let person = { name: "Bob", greet: function () { console.log("Hello! My name is " + this.name); console.log(this); } };

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

Let person = { name: "Bob", greet: function () { setTimeout(function () { console.log("Hello! My name is " + this.name); console.log(this); }, 2000); } }; person.greet();

Если у вас есть опыт работы с JavaScript, то я думаю, что вы понимаете в чем заключается проблема. Все равно, давайте посмотрим на то, что будет в браузере. Ровно две секунды спустя мы увидим в браузере такую картину.

Но почему? Если посмотреть наш код, то логично предположить. что this ссылается на объект person , так как мы используем обычную функцию. Дело в том, что setTimeout() принадлежит объекту window . Если написать так: window.setTimeout() , то как вы думаете на что ссылается thus ? И в консоли мы получим тот же самый результат! В ES5 есть несколько способов решить эту проблему. Мы рассмотрим самый распространенный: Перед setTimeout() я объявлю еще одну переменную that и в качестве значения присвоим this . И теперь, в теле функции вместо this мы укажем that .

Let person = { name: "Bob", greet: function () { let that = this; setTimeout(function () { console.log("Hello! My name is " + that.name); console.log(that); }, 2000); } }; person.greet();

Теперь благодаря замыканию Функция которую мы отправляем в setTimeout() будет иметь доступ к переменной that , значением которой будет this , то есть, в данном случае, объект person .

Можно для наглядности посмотреть на то, что ссылаются наши that и this .

Let person = { name: "Bob", greet: function () { let that = this; setTimeout(function () { console.log("Hello! My name is " + that.name); console.log("It is my That = " + that); console.log("It is my This = " + this); }, 2000); } }; person.greet();

В консоли мы увидим подтверждение:

Мы видим, что this будет объектом окна - This = , а that будет объектом нашего person - That = .

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

Let person = { name: "Bob", greet: function () { setTimeout(() => { console.log("Hello! My name is " + this.name); console.log("It is my This = " + this); }, 2000); } }; person.greet();

В результате мы в консоли увидим:

В графическом примере для стрелочной функции контекстом будет служить объект person , а не объект window . именно поэтому this будет ссылаться на person .

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

В качестве ознакомления, вы можете посмотреть как решил это Babel

Var person = { name: "Bob", greet: function greet() { var _this = this; setTimeout(function () { console.log("Hello! My name is " + _this.name); console.log("It is my This = " + _this); }, 2000); } }; person.greet(); Babel использовал тот же самый метод, что и мы в ES5. Вся разница в том, что мы называли переменную that , а Babel назвал - _this . Благодаря замыканию, функция которую мы отправляем в setTimeout , будет иметь доступ к переменной _this и как следствие - к объекту person .

Думаю, что самое трудное в этой части - это понять как работают замыкания .

Еще некоторые особенности стрелочных функций:
Еще информацию по ES6 и стрелочным функциям вы можете посмотреть в моем посте

An arrow function expression is a syntactically compact alternative to a regular function expression , although without its own bindings to the this , arguments , super , or new.target keywords. Arrow function expressions are ill suited as methods, and they cannot be used as constructors.

Syntax Basic syntax (param1, param2, …, paramN) => { statements } (param1, param2, …, paramN) => expression // equivalent to: => { return expression; } // Parentheses are optional when there"s only one parameter name: (singleParam) => { statements } singleParam => { statements } // The parameter list for a function with no parameters should be written with a pair of parentheses. () => { statements } Advanced syntax // Parenthesize the body of a function to return an object literal expression: params => ({foo: bar}) // Rest parameters and default parameters are supported (param1, param2, ...rest) => { statements } (param1 = defaultValue1, param2, …, paramN = defaultValueN) => { statements } // Destructuring within the parameter list is also supported var f = ( = , {x: c} = {x: a + b}) => a + b + c; f(); // 6 Description

Two factors influenced the introduction of arrow functions: the need for shorter functions and the behavior of the this keyword.

Shorter functions var elements = [ "Hydrogen", "Helium", "Lithium", "Beryllium" ]; // This statement returns the array: elements.map (function(element) { return element.length; }); // The regular function above can be written as the arrow function below elements.map((element) => { return element.length; }); // // When there is only one parameter, we can remove the surrounding parentheses elements.map (element => { return element.length; }); // // When the only statement in an arrow function is `return`, we can remove `return` and remove // the surrounding curly brackets elements.map(element => element.length); // // In this case, because we only need the length property, we can use destructuring parameter: // Notice that the `length` corresponds to the property we want to get whereas the // obviously non-special `lengthFooBArX` is just the name of a variable which can be changed // to any valid variable name you want elements.map (({ length:lengthFooBArX }) => lengthFooBArX); // // This destructuring parameter assignment can also be written as seen below. However, note that in // this example we are not assigning `length` value to the made up property. Instead, the literal name // itself of the variable `length` is used as the property we want to retrieve from the object. elements.map (({ length }) => length); // No separate this

Before arrow functions, every new function defined its own this value based on how the function was called:

  • A new object in the case of a constructor.
  • undefined in strict mode function calls.
  • The base object if the function was called as an "object method".

This proved to be less than ideal with an object-oriented style of programming.

Function Person() { // The Person() constructor defines `this` as an instance of itself. this.age = 0; setInterval(function growUp() { // In non-strict mode, the growUp() function defines `this` // as the global object (because it"s where growUp() is executed.), // which is different from the `this` // defined by the Person() constructor. this.age++; }, 1000); } var p = new Person();

In ECMAScript 3/5, the this issue was fixable by assigning the value in this to a variable that could be closed over.

Function Person() { var that = this; that.age = 0; setInterval(function growUp() { // The callback refers to the `that` variable of which // the value is the expected object. that.age++; }, 1000); } "use strict"; var obj = { a: 10 }; Object.defineProperty(obj, "b", { get: () => { console.log(this.a, typeof this.a, this); // undefined "undefined" Window {...} (or the global object) return this.a + 10; // represents global object "Window", therefore "this.a" returns "undefined" } });

Use of the new operator

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

Var Foo = () => {}; var foo = new Foo(); // TypeError: Foo is not a constructor

Use of prototype property

Arrow functions do not have a prototype property.

Var Foo = () => {}; console.log(Foo.prototype); // undefined

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.

Var func = x => x * x; // concise body syntax, implied "return" var func = (x, y) => { return x + y; }; // with block body, explicit "return" needed

Returning object literals

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

Var func = () => { foo: 1 }; // Calling func() returns undefined! var func = () => { foo: function() {} }; // SyntaxError: function statement requires a name

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:

Var func = () => ({ foo: 1 });

Line breaks

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

Var func = (a, b, c) => 1; // SyntaxError: expected expression, got "=>"

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.

Var func = (a, b, c) => 1; var func = (a, b, c) => (1); var func = (a, b, c) => { return 1 }; var func = (a, b, c) => 1; // no SyntaxError thrown

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.

Let callback; callback = callback || function() {}; // ok callback = callback || () => {}; // SyntaxError: invalid arrow-function arguments callback = callback || (() => {}); // ok

More examples // An empty arrow function returns undefined let empty = () => {}; (() => "foobar")(); // Returns "foobar" // (this is an Immediately Invoked Function Expression) var simple = a => a > 15 ? 15: a; simple(16); // 15 simple(10); // 10 let max = (a, b) => a > b ? a: b; // Easy array filtering, mapping, ... var arr = ; var sum = arr.reduce((a, b) => a + b); // 66 var even = arr.filter(v => v % 2 == 0); // var double = arr.map(v => v * 2); // // More concise promise chains promise.then(a => { // ... }).then(b => { // ... }); // Parameterless arrow functions that are visually easier to parse setTimeout(() => { console.log("I happen sooner"); setTimeout(() => { // deeper code console.log("I happen later"); }, 1); }, 1); Specifications Specification Status Comment
ECMAScript 2015 (6th Edition, ECMA-262)
Standard Initial definition.
ECMAScript Latest Draft (ECMA-262)
The definition of "Arrow Function Definitions" in that specification.
Draft
Browser compatibility

The compatibility table on this page is generated from structured data. If you"d like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.

Update compatibility data on GitHub

Desktop Mobile Server Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet Node.js Arrow functions Trailing comma in parameters
Chrome Full support 45 Edge Full support Yes Firefox Full support 22

Notes

Full support 22

Notes

Notes Prior to Firefox 39, a line terminator (\n) was incorrectly allowed after arrow function arguments. This has been fixed to conform to the ES2015 specification and code like () \n =>
IE No support No Opera Full support 32 Safari Full support 10 WebView Android Full support 45 Chrome Android Full support 45 Firefox Android Full support 22

Notes

Full support 22

Notes

Notes The initial implementation of arrow functions in Firefox made them automatically strict. This has been changed as of Firefox 24. The use of "use strict"; is now required. Notes Prior to Firefox 39, a line terminator (\n) was incorrectly allowed after arrow function arguments. This has been fixed to conform to the ES2015 specification and code like () \n => {} will now throw a SyntaxError in this and later versions.
Opera Android Full support 32 Safari iOS Full support 10 Samsung Internet Android Full support 5.0 nodejs Full support Yes
Chrome Full support 58 Edge ? Firefox Full support 52 IE No support No Opera Full support 45 Safari ? WebView Android Full support 58 Chrome Android Full support 58 Firefox Android Full support 52 Opera Android Full support 43 Safari iOS ? Samsung Internet Android Full support 7.0 nodejs Full support Yes
Legend Full support Full support No support No support Compatibility unknown Compatibility unknown See implementation notes. See implementation notes.
  • Tutorial

Одной из самых интересных частей нового стандарта ECMAScript 6 являются стрелочные функции. Стрелочные функции, как и понятно из названия определяются новым синтаксисом, который использует стрелку => . Однако, помимо отличного синтаксиса, стрелочные функции отличаются от традиционных функций и в других моментах:

  • Лексическое связывание. Значения специальных переменных this , super и arguments определяются не тем, как стрелочные функции были вызваны, а тем, как они были созданы.
  • Неизменяемые this , super и arguments . Значения этих переменных внутри стрелочных функций остаются неизменными на протяжении всего жизненного цикла функции.
  • Стрелочные функции не могут быть использованы как конструктор и кидают ошибку при использовании с оператором new .
  • Недоступность «собственного» значения переменной arguments .
Было несколько причин для введения этих отличий. Первоочередная - это то, что связывание (binding) используется довольно часто в JavaScript. Очень легко потерять нужное значение this при использовании традиционных функций, что может привести к непредсказуемым последствиям. Другая причина, это то, что JS-движки смогут легко оптимизировать выполнение стрелочных функций за счет этих ограничений (в противоположность традиционным функциям, которые могут быть использованы в качестве конструктора и которые свободны для модификации специальных переменных ).


Примечание: Данная статья - это компиляция из вольного перевода статьи Understanding ECMAScript 6 arrow functions и чтения последнего черновика спецификации (January 20, 2014 Draft Rev 22).

Синтаксис В общем случае, синтаксис стрелочных функций выглядит так:

Var fun = (x) => x;
Он очень похож на аналогичный синтаксис в таких языках как Scala, CoffeeScript и на синтаксис lambda-выражений из C#.

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

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

Var reflect = value => value; // эквивалент var reflect = function(value) { return value; }
Когда у стрелочной функции только один аргумент, то он может быть объявлен без скобок. Следующее после стрелки тело функции также может быть без фигурных скобок и может не содержать ключевого слова return .

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

Var sum = (num1, num2) => num1 + num2; // эквивалент var sum = function(num1, num2) { return num1 + num2; };
Функция sum просто суммирует два аргумента. Единственное отличие от предыдущего примера в наличии круглых скобок и запятой (прямо как в традиционных функциях).

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

Var sum = () => 1 + 2; // эквивалент var sum = function() { return 1 + 2; };

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

Var sum = (num1, num2) => { return num1 + num2; } // эквивалент var sum = function(num1, num2) { return num1 + num2; };
Тело функции будет обработано точно так же, как и в случае классических функций, за исключением того, что значения специальных переменных this , super и arguments будут вычисляться по-другому.

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

Var getTempItem = id => ({ id: id, name: "Temp" }); // эквивалент var getTempItem = function(id) { return { id: id, name: "Temp" } };
Помещение литерала объекта в круглые скобки указывает парсеру, что фигурные скобки это не начало традиционного синтаксиса для тела функции, а начало литерала.

Переменное число параметров Так как «собственный» объект arguments не доступен внутри стрелочной функции (значение arguments лексически связано со значением arguments традиционной функции, внутри которой стрелочная функция была объявлена), то для стрелочных функций с переменным числом параметров нужно использовать rest -паттерн из шаблонов деструктуризации . Пример:

Var getTempItems = (...rest) => rest; // эквивалент var getTempItems = function() { return .slice.apply(arguments) };

Шаблон деструктуризации в качестве параметраВ рамках данной статьи мы не рассматриваем шаблоны деструктуризации - вы можете почитать про них в статье Обзор ECMAScript 6, следующей версии JavaScript , хотя эта информация частично устарела.

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

Var a = ({a}) => a; var b = ([b]) => b;

Использование стрелочных функций Установка контекста Одним из частых сценариев в JavaScript является установка правильного значения this внутри функции (связывание). Поскольку значение this может быть изменено, то, в зависимости от контекста исполнения функции, возможно ошибочно воздействовать на один объект, когда вы имели ввиду совершенно другой. Посмотрите на следующий пример:

Var pageHandler = { id: "123456" , init: function() { document.addEventListener("click", function(event) { this.doSomething(event.type); // ошибка }); } , doSomething: function(type) { console.log("Handling " + type + " for " + this.id) } };
В приведённом коде объект pageHandler должен обрабатывать клики на странице. Метод init() навешивает обработчик на нужное событие, который внутри себя вызывает this.doSomething() . Однако код отработает неправильно. Ссылка на this.doSomething() не валидна, поскольку this указывает на объект document внутри обработчика события вместо планируемого pageHandler . При попытке выполнить этот код, вы получите ошибку, поскольку объект document не имеет метода doSomething .

Вы можете завязать значение this на объекте pageHandler используя handleEvent или вызвав у функции стандартный метод bind() :

Var pageHandler = { id: "123456" , init: function() { document.addEventListener("click", (function(event) { this.doSomething(event.type); // error }).bind(this)); } , doSomething: function(type) { console.log("Handling " + type + " for " + this.id) } };
Теперь код работает так, как и задумывалось, но выглядит более громоздко. Кроме того, вызывая bind(this) вы каждый раз создаёте новую функцию, значение this которой завязано на значении pageHandler , но зато код работает так, как вы задумывали.

Стрелочные функции решают проблему более элегантным способом, поскольку используют лексическое связывание значения this (а также super и arguments ) и его значение определяется значением this в том месте, где стрелочная функция была создана. Например:

Var pageHandler = { id: "123456" , init: function() { document.addEventListener("click", event => this.doSomething(event.type)); } , doSomething: function(type) { console.log("Handling " + type + " for " + this.id) } };
В этом примере обработчик это стрелочная функция в которой вызывается this.doSomething() . Значение this будет тем же, что и в функции init() , и код в данном примере отработает правильно, аналогично тому, который использовал bind() . Вне зависимости от того, возвращает вызов this.doSomething() значение или нет, выражение внутри тела стрелочной функции не нужно обрамлять в фигурные скобки.

Кроме того, пример выше ещё и эффективнее вызова bind() , потому что для браузера он аналогичен следующему коду:

Var pageHandler = { id: "123456" , init: function() { var self = this; document.addEventListener("click", function(event) { return self.doSomething(event.type) }); } , doSomething: function(type) { console.log("Handling " + type + " for " + this.id) } };
То есть не происходит создание новой функции, как в случае с вызовом bind() .

«Прокидывание» контекста между несколькими вызовами Очевидно, что можно вкладывать одну стрелочную функцию в другую, тем самым «прокидывая» значение this через них:

Var obj = { arr1: , arr2: ["a", "b", "c"] , concatenate: function(a, b){ return a + "|" + b } , intersection: function() { return this.arr1.reduce((sum, v1) => // arrow function 1 this.arr2.reduce((sum, v2) => { // arrow function 2 sum.push(this.concatenate(v1, v2)) return sum; } , sum) , ) } }; var arrSum = obj.intersection();//["1|a", "1|b", "1|c", "2|a", "2|b", "2|c", "3|a", "3|b", "3|c"]

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

Var result = values.sort(function(a, b) { return a - b });
Довольно многословно для простой операции. Сравните с короткой записью стрелочной функции:

Var result = values.sort((a, b) => a - b);
Использование таких методов, как массивные sort() , map() , reduce() и так далее, может быть упрощено с использованием короткого синтаксиса стрелочной функции.

Другие особенности стрелочных функций Несмотря на то, что стрелочные функции отличаются от традиционных функций, у них есть общие черты:
  • Оператор typeof вернёт "function" для стрелочной функции
  • Стрелочная функция также экземпляр «класса» Function, поэтому instanceof сработает так же как, и с традиционной функцией
  • Вы всё ещё можете использовать методы call() , apply() , и bind() , однако помните, что они не будут влиять на значение this
  • Вы можете использовать метод toMethod() , однако он не будет менять значение super (метод toMethod() введён в es6 и не рассматривается в рамках данной статьи ).
Существенным отличием от традиционных функций является то, что попытка вызвать стрелочную функцию с указанием оператора new вызовет ошибку исполнения.Итог Стрелочные функции это одно из интереснейших нововведений в ECMAScript 6, которое, имея краткий синтаксис определения, упростит передачу функций в качестве значения параметра другой функции.

Лаконичный синтаксис позволит писать сложные вещи ещё сложнее проще. Например, так будет выглядеть генератор идентификаторов (который на es5 выглядит куда многословней):

Let idGen = (start = 0, id = start, reset = (newId = start) => id = newId, next = () => id++) => ({reset, next}); let gen = idGen(100); console.log(gen.next(), gen.next(), gen.reset(10), gen.next());//100 101 10 10
А лексическое связывание закроет один из самых больших источников боли и разочарования для разработчиков, а так же улучшит производительность за счёт оптимизации на уровне js-движка.


Если вы хотите попробовать стрелочные функции, то можете выполнить вышеуказанные примеры в консоли Firefox, который на данный момент (02.2014 FF28) почти полноценно поддерживает стрелочные функции (FF28 неправильно вычисляет значение arguments ).

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

Теги: Добавить метки