До сих пор в Learning ES6 series мы рассматривали область видимости блока, параметры по умолчанию, деструктуризацию, операторы отдыха и распространения, оператор for-of и литералы шаблонов. Сегодня давайте продолжим серию статей о стрелочных функциях, также известных как толстая стрелка.

TL;DR

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

let squares = [1, 2, 3].map(x => x * x);

Эквивалентно этому в ES5:

var squares = [1, 2, 3].map(function (x) {
 return x * x;
});

Как видите, многословие в выражениях функций в старом стиле удалено, и осталась жирная стрелка (= ›), соединяющая два основных компонента функции: аргументы и тело функции.

Вы найдете наибольшую полезность в стрелочных функциях там, где функции принимают анонимную функцию обратного вызова, например, в обработчиках событий (например, onClick, $ .ajax и т. Д.) И в процессорах массивов (например, в map, sort и т. Д.)

Хотите узнать о стрелочных функциях более подробно? Не стесняйтесь проверить страницу Примеры кода стрелочных функций, на которой подробно описаны функции. Вы также можете попробовать свои силы в ES6 katas, чтобы увидеть, сколько вы действительно узнали.

Давайте продолжим!

Синтаксис стрелочной функции

Функции со стрелками могут иметь несколько комбинаций синтаксиса в зависимости от потребностей функции.

Аргументы стрелочной функции

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

// two or more arguments
var sum = [9, 8, 7].reduce((memo, value) => memo + value, 0);
// no arguments
var getRandom = () => Math.random() * 100;

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

var valuesShallowCopy = [‘foo’, ‘bar’].map(x => x);

Полная форма тела стрелочной функции поддерживает несколько операторов в блоке:

$(‘#deleteButton’).click(event => {
 if (confirm(‘Are you sure?’)) {
 clearAll();
 }
});

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

Тело функции стрелки

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

var activeCompanies = companies.filter(company => company.active);

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

// BUG! BUG! BUG!
// will return an array of undefined values since the code block
// is empty and returns nothing
var myObjects = myArray.map(value => {});
console.log(myObjects);
// Correct!
// will return an array of empty objects
var myObjects = myArray.map(puppy => ( {} ) );
console.log(myObjects);
// BUG! BUG! BUG!
// will return an array of undefined values since the code block
// looks like it has a label of “foo” and an expression of “x” that is
// NOT returned.
console.log([4, 5, 1].map(x => {foo: x} ));
// Correct!
// will return an array of objects with “foo” as key and number
// as value
console.log([4, 5, 1].map(x => ( {foo: x} ) ));

Вы обнаружите, что стрелочные функции наиболее удобны при использовании в качестве анонимной функции обратного вызова. Различные методы массивов функционального программирования более высокого порядка, представленные в ECMAScript 5 (например, map, filter, forEach, reduce и т. Д.), Хорошо работают со стрелочными функциями. Стрелочные функции также могут использоваться в качестве функций обратного вызова для обработчиков событий, но обычно в мире ООП они в конечном итоге являются (частными) методами в вашем классе, чтобы их можно было правильно тестировать. Стрелочные функции не предназначены для методов на основе прототипов или классов. Мы немного разберемся, почему.

Немедленно вызываемые стрелочные функции (IIAF)

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

(function(message) {
 // print out each character of message
 for (let char of message) {
 console.log(char);
 }
}) (‘hello’);

То же самое можно сделать и со стрелочными функциями:

(message => {
 // print out each character of message
 for (let char of message) {
 console.log(char);
 }
}) (‘hello’);

Единственное, что нужно знать, - это расположение скобок. Они должны заключить выражение функции стрелки перед круглыми скобками, вызывающими вызов (перед частью («привет»)). В случае IIFE круглые скобки могут также охватывать весь IIFE, включая вызов функции.

Лексическое это

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

Давайте посмотрим на проблему с кодированием JavaScript, которая должна помочь в объяснении:

var car = {
 speed: 0,
 accelerate: function() {
 this.accelerator = setInterval(
 function() {
 // BUG! _this_ is not what we expect.
 // In non-strict mode, _this_ is the
 // global object. In strict mode _this_
 // is undefined. In neither case is _this_
 // the car object we want.
 this.speed++;
 console.log(this.speed);
 },
 100
 );
 },
 cruise: function() {
 clearInterval(this.accelerator);
 console.log(`cruising at ${this.speed} mph`);
 }
};
car.accelerate();
setTimeout(function() { car.cruise(); }, 5000);

Каждый начинающий разработчик JavaScript сталкивался с этой проблемой, потому что не знал ничего лучшего. Каждый опытный разработчик случайно сталкивался с этой проблемой, хотя и знал лучше. В ES3 подход к решению этой проблемы заключался в том, чтобы сохранить ссылку на this в переменной с именем self, чтобы она была доступна в области действия анонимной функции:

var car = {
 speed: 0,
 accelerate: function() {
 // store a reference to `this` in a variable that will be
 // be available for use within the anonymous function
 // callback
 var self = this;
 this.accelerator = setInterval(
 function() {
 self.speed++;
 console.log(self.speed);
 },
 100
 );
 },
 cruise: function() {
 clearInterval(this.accelerator);
 console.log(`cruising at ${this.speed} mph`);
 }
};
car.accelerate();
setTimeout(function() { car.cruise(); }, 5000);

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

var car = {
 speed: 0,
 accelerate: function() {
 this.accelerator = setInterval(
 // bind returns a new “cloned” function
 // such that _this_ within the function
 // matches _this_ outside of it by passing it
 // as the argument.
 (function() {
 this.speed++;
 console.log(this.speed);
 }).bind(this),
 100
 );
 },
 cruise: function() {
 clearInterval(this.accelerator);
 console.log(`cruising at ${this.speed} mph`);
 }
};
car.accelerate();
setTimeout(function() { car.cruise(); }, 5000);

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

var car = {
 speed: 0,
 accelerate: function() {
 this.accelerator = setInterval(
 () => {
 // _this_ is the same as it is outside
 // of the arrow function!
 this.speed++;
 console.log(this.speed);
 },
 100
 );
 },
 cruise: function() {
 clearInterval(this.accelerator);
 console.log(`cruising at ${this.speed} mph`);
 }
};
car.accelerate();
setTimeout(() => car.cruise(), 5000);

Теперь все счастливы. Стоит отметить, что транспиляторы используют решение ES3 для передачи лексического this. Единственная разница в том, что они используют автоматически сгенерированное имя переменной (например, $ __ 1) вместо self, that или m. Они не используют метод привязки ES5, что делает транспилированный код совместимым с ES3. Это означает, что перенесенный код будет работать в браузерах, отличных от ES5, таких как IE8.

Определение стрелочных функций

Хотя стрелочные функции существенно отличаются по синтаксису и для этого используется лексическая область видимости (а также для других конструкций), они по-прежнему идентифицируются как функции. Например, typeof и instanceof говорят, что стрелочные функции являются функциями:

console.log(typeof function() { }); // ‘function’
console.log(typeof (() => {})); // ‘function’
console.log(function() { } instanceof Function); // true
console.log((() => {}) instanceof Function); // true

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

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

// 2nd parameter is `undefined`, triggers
// default of 100.
// output: 2, 200, 10
console.log(
 [1, undefined, 5].map(
 (x=100) => x * 2
 )
);

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

Лексические аргументы

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

Вместо этого объект arguments «наследуется» от лексической области действия содержащей функцию точно так же. Этот объект аргументов будет доступен независимо от того, где в дальнейшем будет выполняться стрелочная функция. Давайте посмотрим на надуманный пример:

function genArrowReturningLexArgs() {
 // returns an arrow function expression
 // which itself returns the arguments used
 // when generating the arrow function
 return () => arguments;
}
var arrowFunction = genArrowReturningLexArgs(5, ‘foo’, [5,4,3]);
// log arguments object with
// 5, ‘foo’, and [5,4,3]
console.log(arrowFunction());

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

JavaScript догоняет

JavaScript не изобретал функциональные выражения или лямбда-функции. На самом деле они были впервые представлены Lisp еще в 1958 году. Однако бурный рост высокоинтерактивного клиентского программирования на JavaScript с асинхронными обратными вызовами функций обработки событий привел к своего рода возрождению лямбда-функций. JavaScript и лямбда-функции стали настолько популярными, что другие давние языки программирования начали использовать лямбда-функции:

  • C# — a => a > 0
  • Python - лямбда a: a ›0
  • Java - a - ›a› 0
  • JavaScript - функция (a) {return a ›0; }

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

Сводка различий в функциях стрелок

Хотя мы узнали, что стрелочные функции на самом деле идентифицируются как функции, между ними и традиционными функциями есть важные различия. Подведем итоги:

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

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

Нет объекта arguments. Вы не можете получить доступ к аргументам через объект arguments, вы должны использовать обычные именованные параметры или другие функции ES6, такие как параметры отдыха.

Не новинка. Стрелочные функции не имеют внутреннего метода [[Construct]] и поэтому не могут использоваться в качестве конструкторов прототипов. Стрелочные функции выдают ошибку при использовании с new.

Поддержка движка JavaScript

Согласно таблице совместимости ECMAScript 6, все современные движки JavaScript поддерживают стрелочные функции, кроме Safari и iOS9. Мы, конечно, знаем, что IE 11 и ниже не поддерживает ни одну из функций ES6.

Дополнительные ресурсы

Вы можете посетить страницу Learning ES6 examples page для Learning ES6 Github repo, где вы найдете весь код, используемый в этой статье, который изначально работает в браузере. (для тех, кто поддерживает стрелочные функции). Есть также примеры, проходящие через транспиляцию Babel и Traceur.

Вы также можете попрактиковаться во всем, что узнали о шаблонных литералах и шаблонах с тегами, на ES6 Katas. Он использует подход TDD (разработка через тестирование), позволяющий реализовать функции ES6 таким образом, чтобы все тесты проходили успешно. Это действительно здорово!

Другие очень полезные ресурсы:

Далее…

Мы продолжим серию Изучение ES6, рассмотрев расширенные литералы объектов. Они улучшены, потому что мы можем писать меньше кода для их создания!

- Бен Илегбоду, старший инженер-программист Eventbrite