- Стрелочная функция
- Совет: Необходимость стрелочных функций
- Совет: Опасность стрелочных функций
- Совет: Стрелочные функции и библиотеки, которые используют
this
- Совет: Стрелочные функции и наследование
- Совет: Быстрый возврат объекта
Заботливо называемая толстой стрелкой (потому что ->
это тонкая стрелка и =>
это толстая стрелка), а также называемая лямбда-функцией (как в других языках). Наиболее часто используемый вариант ()=>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
, например итератор в jQuery
(один из примеров https://api.jquery.com/jquery.each/) будет использовать 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
});