Стрелочная функция¶
Заботливо называемая толстой стрелкой (потому что ->
это тонкая стрелка и =>
это толстая стрелка), а также называемая лямбда-функцией (как в других языках). Наиболее часто используемый вариант () => something
. Мотивация для использования толстой стрелки - это:
- Вам не нужно печатать
function
- Лексически отражает значение
this
- Лексически отражает значение
arguments
Для языка, который претендует на функциональность, в JavaScript вы обычно набираете function
достаточно часто. Стрелочная функция упрощает создание функции
var inc = (x) => x + 1;
this
традиционно был болезненным пунктом в JavaScript. Как однажды сказал мудрый человек: "Я ненавижу JavaScript, так как он слишком легко теряет значение this
". Стрелочная функция решает эту проблему, фиксируя значение this
из окружающего контекста. Рассмотрим чистый JavaScript класс:
function Person(age) {
this.age = age;
this.growOld = function () {
this.age++;
};
}
var person = new Person(1);
setTimeout(person.growOld, 1000);
setTimeout(function () {
console.log(person.age);
}, 2000); // 1, должен быть 2
Если вы запустите этот код в браузере, this
внутри функции будет указывать на window
, потому что window
будет контекстом выполнения функции growOld
. Исправить это можно использованием стрелочной функции:
function Person(age) {
this.age = age;
this.growOld = () => {
this.age++;
};
}
var person = new Person(1);
setTimeout(person.growOld, 1000);
setTimeout(function () {
console.log(person.age);
}, 2000); // 2
Это работает, потому что в стрелочной функции this
ссылается на окружающий контекст. Это равнозначно следующему коду на JavaScript (вы бы написали это сами, если бы у вас не было TypeScript):
function Person(age) {
this.age = age;
var _this = this; // сохраняем this
this.growOld = function () {
_this.age++; // используем сохраненный this
};
}
var person = new Person(1);
setTimeout(person.growOld, 1000);
setTimeout(function () {
console.log(person.age);
}, 2000); // 2
Обратите внимание, что используя TypeScript, вы можете писать более приятный код и комбинировать стрелочные функции и фичи классов:
class Person {
constructor(public age: number) {}
growOld = () => {
this.age++;
};
}
var person = new Person(1);
setTimeout(person.growOld, 1000);
setTimeout(function () {
console.log(person.age);
}, 2000); // 2
Совет: Необходимость стрелочных функций¶
Помимо краткого синтаксиса, вам следует использовать стрелочную функцию только в том случае, если вы хотите передать функцию кому-то для вызова. Фактически:
var growOld = person.growOld;
// Позже кто-то вызовет ее:
growOld();
Если же вы сами ее вызываете:
person.growOld();
тогда this
будет использовать корректный контекст вызова (в примере это person
).
Совет: Опасность стрелочных функций¶
На самом деле, если вы хотите, чтобы именно this
был контекстом вызова, тогда не нужно использовать стрелочные функции. Это относится к обратным вызовам, используемым библиотеками типа jquery, underscore, mocha и другими. Если в документации указан вызов функции для this
, тогда вам следует использовать обычную function
вместо стрелочной функции. Точно также, если вы планируете использовать arguments
, то не прибегайте к стрелочным функциям.
Совет: Стрелочные функции и библиотеки, которые используют this
¶
Многие библиотеки используют this
, например, итератор в jQuery
(один из примеров) будет использовать this
, чтобы передать вам объект, который он в данный момент перебирает. В данном случае, если вам нужен доступ и к окружающему контексту, и к this
, переданному из библиотеки, просто используйте дополнительную переменную, например, _self
, для хранения ссылки на окружающий контекст.
let _self = this;
something.each(function () {
console.log(_self); // внешний контекст
console.log(this); // контекст функции .each()
});
Совет: Стрелочные функции и наследование¶
Стрелочные функции как методы классов прекрасно работают с наследованием:
class Adder {
constructor(public a: number) {}
add = (b: number): number => {
return this.a + b;
};
}
class Child extends Adder {
callAdd(b: number) {
return this.add(b);
}
}
// Демонстрация работы
const child = new Child(123);
console.log(child.callAdd(123)); // 246
Однако, они не работают с super
, когда вы пытаетесь переопределить метод базового класса в дочернем. Свойства копируются на this
. Поскольку существует только один this
, такие функции не могут участвовать в вызове super
(super
работает только с членами-прототипами). Вы можете легко обойти это, создав копию метода перед тем, как переопределить его.
class Adder {
constructor(public a: number) {}
// Теперь эту функцию можно безопасно передавать
add = (b: number): number => {
return this.a + b;
};
}
class ExtendedAdder extends Adder {
// Создание копии метода родителького класса
private superAdd = this.add;
// Переопределение
add = (b: number): number => {
return this.superAdd(b);
};
}
Совет: Быстрый возврат объекта¶
Иногда вам нужна функция, которая возвращает простой объект. Что-то вроде
// неправильный способ сделать это
var foo = () => {
bar: 123;
};
парсится как блок, содержащий JavaScript Label во время выполнения (в соответствии со спецификацией JavaScript).
Вы в любом случае получите ошибку компилятора TypeScript о "неиспользуемой метке (label)". Метки - это старая (и в основном неиспользуемая) функция JavaScript, которую вы можете игнорировать как современный GOTO (который опытные разработчики считают плохим 🌹).
Вы можете исправить ошибку, обернув объект в ()
:
// корректно 🌹
var foo = () => ({
bar: 123,
});