Перейти к содержанию

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

Заботливо называемая толстой стрелкой (потому что -> это тонкая стрелка и => это толстая стрелка), а также называемая лямбда-функцией (как в других языках). Наиболее часто используемый вариант () => something. Мотивация для использования толстой стрелки - это:

  1. Вам не нужно печатать function
  2. Лексически отражает значение this
  3. Лексически отражает значение arguments

Для языка, который претендует на функциональность, в JavaScript вы обычно набираете function достаточно часто. Стрелочная функция упрощает создание функции

1
var inc = (x) => x + 1;

this традиционно был болезненным пунктом в JavaScript. Как однажды сказал мудрый человек: "Я ненавижу JavaScript, так как он слишком легко теряет значение this". Стрелочная функция решает эту проблему, фиксируя значение this из окружающего контекста. Рассмотрим чистый JavaScript класс:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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. Исправить это можно использованием стрелочной функции:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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, вы можете писать более приятный код и комбинировать стрелочные функции и фичи классов:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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

Совет: Необходимость стрелочных функций

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

1
2
3
var growOld = person.growOld;
// Позже кто-то вызовет ее:
growOld();

Если же вы сами ее вызываете:

1
person.growOld();

тогда this будет использовать корректный контекст вызова (в примере это person).

Совет: Опасность стрелочных функций

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

Совет: Стрелочные функции и библиотеки, которые используют this

Многие библиотеки используют this, например, итератор в jQuery (один из примеров) будет использовать this, чтобы передать вам объект, который он в данный момент перебирает. В данном случае, если вам нужен доступ и к окружающему контексту, и к this, переданному из библиотеки, просто используйте дополнительную переменную, например, _self, для хранения ссылки на окружающий контекст.

1
2
3
4
5
let _self = this;
something.each(function () {
    console.log(_self); // внешний контекст
    console.log(this); // контекст функции .each()
});

Совет: Стрелочные функции и наследование

Стрелочные функции как методы классов прекрасно работают с наследованием:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 работает только с членами-прототипами). Вы можете легко обойти это, создав копию метода перед тем, как переопределить его.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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);
    };
}

Совет: Быстрый возврат объекта

Иногда вам нужна функция, которая возвращает простой объект. Что-то вроде

1
2
3
4
// неправильный способ сделать это
var foo = () => {
    bar: 123;
};

парсится как блок, содержащий JavaScript Label во время выполнения (в соответствии со спецификацией JavaScript).

Вы в любом случае получите ошибку компилятора TypeScript о "неиспользуемой метке (label)". Метки - это старая (и в основном неиспользуемая) функция JavaScript, которую вы можете игнорировать как современный GOTO (который опытные разработчики считают плохим 🌹).

Вы можете исправить ошибку, обернув объект в ():

1
2
3
4
// корректно 🌹
var foo = () => ({
    bar: 123,
});

Комментарии