- Стрелочная функция
- Совет: Необходимость стрелочных функций
- Совет: Опасность стрелочных функций
- Совет: Стрелочные функции и библиотеки, которые используют
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
});