Стрелочные функции против Function
В JavaScript стрелочные функции (arrow function) предоставляют краткий синтаксис для выражений анонимных функций, освобожденных от их багажа ООП. Они представляют собой синтаксический сахар для подмножества функциональных возможностей. Оба могут быть использованы в качестве замыканий, захватывающих переменные внешней области.
Arrow function являются частью стандарта ECMAScript 2015, также известного как ES6. Мы распакуем варианты синтаксиса стрелочной функции ES6 в их аналогичную функциональную реализацию и обсудим различия.
Статья предполагает знакомство с традиционными функциями и основывается на предварительных знаниях, проводя параллели между двумя языковыми механизмами.
Синтаксис
Синтаксис «fat arrow» =>
предназначен для стрелочных функций, отсюда и название.
Объявление стрелочной функции:
(arg1, arg2, ..., argN) => expression
Эквивалент анонимной function:
(function (arg1, arg2, ..., argN) {
return expression;
}).bind(this)
Здесь многое происходит: пропущенные ключевые слова, неявный оператор return
, this
привязка контекста. Каждый аспект обсуждается отдельно ниже.
Семантика: выражение return
В отличие от обычных функций (анонимных или иных), arrow function неявно возвращают вычисляемое выражение без необходимости использования оператора return
.
Стрелочная функция:
(arg1, arg2, ..., argN) => expression
Эквивалент анонимной function:
function (arg1, arg2, ..., argN) {
return expression;
}
Как только вы привыкнете к синтаксису, вы оцените, насколько короче становится код, и никогда не захотите возвращаться к нему.
Оператор block
Синтаксис короткого возвращаемого выражения не может представлять последовательность инструкций. Вот тут-то и появляется знакомый оператор block {}
. В фигурных скобках вам пришлось бы явно return
результат функции.
(arg1, arg2, ..., argN) => {
let result = doSomething();
doDependentThing(result);
return result;
}
Эквивалент анонимной function:
function (arg1, arg2, ..., argN) {
let result = doSomething();
doDependentThing(result);
return result;
}
Теперь функции выглядят более похожими.
Выражение object
Функции часто возвращают вновь созданные объекты. Есть одна загвоздка: обозначение объявления объекта {}
неотличимо от синтаксиса блочного оператора. Решение состоит в том, чтобы окружить встроенный объект с помощью ()
, чтобы сделать его выражением.
Стрелочная функция:
(arg1, arg2, ..., argN) => ({
prop1: value1,
prop2: value2,
...,
propN: valueN
})
Эквивалент анонимной function:
function (arg1, arg2, ..., argN) {
return {
prop1: value1,
prop2: value2,
...,
propN: valueN
};
}
Один аргумент
Существует дополнительный синтаксический сахар для особого случая arrow function, имеющей только один аргумент. Вы можете опустить круглые скобки ()
вокруг аргумента.
Стрелочная функция:
arg => expression
Эквивалент анонимной function:
function (arg) {
return expression;
}
Без аргументов
Стрелочная функция без аргументов - это просто крайний случай пустых круглых скобок. В отличие от синтаксиса с одним аргументом, здесь требуются круглые скобки.
Стрелочная функция:
() => expression
Эквивалент анонимной function:
function () {
return expression;
}
Привязка контекста
Давайте поговорим о слоне в комнате – в контексте this
. Стрелочная функция в стороне, это (каламбур) всегда было запутанной темой в JavaScript.
Функции имеют доступ к специальной переменной this
, содержащей контекст, назначенный во время выполнения. Проблема в том, что значение меняется в зависимости от того, как вызывается функция, что подвержено ошибкам и часто нежелательно.
Поскольку обратные вызовы являются основным вариантом использования, в большинстве случаев вы хотели бы получить доступ к контексту this
, определяемому во время объявления, а не при вызове.
Вы обнаружите, что добавляете в свой код следующий шаблон закрытия:
let self = this;
let callback = function () {
self.doSomething();
};
или повторная привязка, чтобы избежать self
в обратном вызове:
let callback = function () {
this.doSomething();
};
callback = callback.bind(this);
Напротив, стрелочные функции не предоставляют собственного контекста this
и вместо этого наследуют текущую «лексическую» область видимости. Они, естественно, подходят для встроенных обратных вызовов.
Эквивалент анонимной function:
let callback = () => void this.doSomething();
Оператор void
отбрасывает результат, возвращаемый this.doSomething()
, если таковое имеется. На практике, однако, передача результата часто является приемлемой, и void
может быть опущен. Оператор block {}
- это еще один (возможно, лучший) способ игнорировать результат.
Методы class
Функции со стрелками удобны в классах из-за природы this
контекста. Обычные методы склонны к потере контекста класса при вызове извне методов класса. Методы arrow невосприимчивы к этой проблеме.
Синтаксис метода arrow - это не что иное, как объявление свойства класса со стрелочной функцией, назначенной вместо значения. Обратите внимание, что свойства класса представлены в спецификации ECMAScript 2017.
Метод arrow (свойство стрелочной функции):
class Example {
constructor(arg) {
this.arg = arg;
}
callback = () => {
console.log(this.arg);
}
}
Эквивалентный метод класса ES6:
class Example {
constructor(arg) {
this.arg = arg;
this.callback = this.callback.bind(this);
}
callback() {
console.log(this.arg);
}
}
Пример: Рефакторинг цикла
Одиночный аргумент довольно распространен в обратных вызовах метода массива, таких как map()
и его двоюродных братьев, которые выполняют итерацию по элементам.
Цикл по массиву элементов:
let ids = [];
for (let i = 0; i < items.length; i++) {
ids.push(items[i].id);
}
return ids;
Эквивалентная традиционная реализация function:
let ids = items.map(function (item) {
return item.id;
});
Эквивалентная реализация стрелочной функции:
let ids = items.map(item => item.id);
Этот пример наглядно демонстрирует уровень сжатия кода, обеспечиваемый стрелочной функцией, без ущерба для удобочитаемости и даже улучшая ее.