Стрелочные функции и this
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Стрелочная функция — это не просто короткая запись function: сам синтаксис разобран в статье «Стрелочные функции». Главное отличие спрятано в одном слове: у стрелки нет собственного this, и именно отсюда растут все её достоинства и все её ловушки.
Обычная функция получает this в момент вызова: кто вызвал — тот и стал this. Стрелка так не умеет вовсе. У неё нет собственной ячейки под this, поэтому имя this внутри стрелки читается как обычная внешняя переменная — из того кода, где стрелка написана.
Отсюда следствие, которое ломает интуицию: call, apply и bind не меняют this стрелки. Это не ошибка — просто нечего менять. Что эти три метода делают с обычной функцией, разобрано в статье «call, apply и bind».
Зато стрелка, созданная внутри метода, наследует его this и держится за него, куда бы её потом ни передали.
Это главный сценарий стрелок. Колбэк, переданный в map, forEach или then, вызывается не вашим объектом, а внутренностями метода — и обычная функция теряет this. Раньше это лечили строкой const self = this или вызовом bind, теперь достаточно стрелки. Полный список мест, где контекст теряется, — в статье «Потеря контекста и как её чинить».
Разницу удобно увидеть в одном методе: сравним два колбэка, отличающихся только формой записи.
Метод, записанный стрелкой, выглядит современно и работает неправильно. Объектный литерал не создаёт this, поэтому стрелка возьмёт его снаружи: из модуля, из глобального кода — откуда угодно, только не из объекта.
В обычном браузерном скрипте обращение к this.value в таком «методе» тихо вернёт undefined, а в модуле уронит код ошибкой. Оба исхода одинаково бесполезны, поэтому методы объекта пишем краткой записью: viaMethod() { ... }.
Правило на салфетке: нужен свой this — обычная функция, нужен чужой this — стрелка.
У стрелки нет не только this. Псевдомассива arguments у неё тоже нет — и это скорее подарок: остаточные параметры делают то же самое, но дают настоящий массив с map и filter.
Ошибки не будет, когда стрелка лежит внутри обычной функции: имя arguments найдётся снаружи — ровно так же, как this. А вот в глобальном коде такая стрелка бросит ReferenceError.
Стрелка — не конструктор. У неё нет механики создания объектов и нет свойства prototype, поэтому new бросает TypeError сразу, даже не заглянув в тело функции.
Поле класса вычисляется отдельно для каждого экземпляра, и this внутри него — сам экземпляр. Стрелка запоминает его навсегда, поэтому такой метод можно оторвать от объекта и передать куда угодно.
Выбор почти всегда сводится к одному вопросу: нужен ли этой функции собственный this. Если да — обычная функция, если нет или нужен внешний — стрелка.
| Ситуация | Что писать | Почему |
|---|---|---|
| Колбэк внутри метода (map, forEach, then) | стрелка | this остаётся тем же объектом |
| Метод объекта | краткая запись метода | нужен this вызывающего объекта |
| Конструктор | function или class | стрелку нельзя вызвать через new |
| Обработчик события, которому нужен элемент | обычная функция | стрелка не получит элемент в this |
| Короткое преобразование данных | стрелка | меньше шума, а this и не нужен |
| Функция с произвольным числом аргументов через arguments | обычная функция | у стрелки нет своего arguments |
Заметьте: короткая запись — приятный бонус, а не причина выбора. Стрелка отличается от function поведением, а не длиной.
- У стрелки нет собственного this — она берёт его из места, где написана; call, apply и bind этого не меняют.
- Поэтому стрелка отлично работает колбэком внутри метода: this остаётся тем же объектом, никаких self и bind.
- И по той же причине она плоха как метод объекта: this будет внешним, а не самим объектом.
- Своего arguments у стрелки тоже нет — вместо него берите остаточные параметры (...args).
- Стрелку нельзя вызвать через new: для конструкторов остаются function и class.
1/3Внутри метода объекта колбэк для forEach написан обычной функцией. Чему будет равен this внутри такого колбэка?
2/3Можно ли задать this стрелочной функции через call, apply или bind?
3/3В объектном литерале метод записали стрелкой: greet: () => this.name. Почему имя пользователя почти наверняка не выведется?
Убрать костыль self
Код работает, но держится на переменной self — старом приёме из времён до стрелок. Перепишите метод intro так, чтобы self исчезла, а результат остался прежним.
Колбэк для map заменяем стрелкой: она берёт this из метода intro, где this — это team. Промежуточная переменная становится не нужна.
Обратите внимание: сам intro остался обычным методом. Сделай мы стрелкой и его — this перестал бы указывать на team, и никакой колбэк уже не помог бы.
Предскажите this
Не запуская код, скажите, что выведет последняя строка и почему значения различаются.
Выведет false true.
ping — стрелка, лежащая прямо в литерале. Её this взят из кода вокруг объекта (модуль или глобальная область), а не из вызова gadget.ping(), поэтому сравнение с gadget даёт false.
echo — обычный метод, и при вызове gadget.echo() его this равен gadget. Стрелка inner создана внутри этого вызова и наследует тот же this, поэтому получается true.