Стрелочные функции и this

контекст≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Стрелочная функция — это не просто короткая запись function: сам синтаксис разобран в статье «Стрелочные функции». Главное отличие спрятано в одном слове: у стрелки нет собственного this, и именно отсюда растут все её достоинства и все её ловушки.

Разделы темы:·

Обычная функция получает this в момент вызова: кто вызвал — тот и стал this. Стрелка так не умеет вовсе. У неё нет собственной ячейки под this, поэтому имя this внутри стрелки читается как обычная внешняя переменная — из того кода, где стрелка написана.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Отсюда следствие, которое ломает интуицию: call, apply и bind не меняют this стрелки. Это не ошибка — просто нечего менять. Что эти три метода делают с обычной функцией, разобрано в статье «call, apply и bind».

Зато стрелка, созданная внутри метода, наследует его this и держится за него, куда бы её потом ни передали.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Это главный сценарий стрелок. Колбэк, переданный в map, forEach или then, вызывается не вашим объектом, а внутренностями метода — и обычная функция теряет this. Раньше это лечили строкой const self = this или вызовом bind, теперь достаточно стрелки. Полный список мест, где контекст теряется, — в статье «Потеря контекста и как её чинить».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Разницу удобно увидеть в одном методе: сравним два колбэка, отличающихся только формой записи.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Метод, записанный стрелкой, выглядит современно и работает неправильно. Объектный литерал не создаёт this, поэтому стрелка возьмёт его снаружи: из модуля, из глобального кода — откуда угодно, только не из объекта.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

В обычном браузерном скрипте обращение к this.value в таком «методе» тихо вернёт undefined, а в модуле уронит код ошибкой. Оба исхода одинаково бесполезны, поэтому методы объекта пишем краткой записью: viaMethod() { ... }.

Правило на салфетке: нужен свой this — обычная функция, нужен чужой this — стрелка.

У стрелки нет не только this. Псевдомассива arguments у неё тоже нет — и это скорее подарок: остаточные параметры делают то же самое, но дают настоящий массив с map и filter.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Ошибки не будет, когда стрелка лежит внутри обычной функции: имя arguments найдётся снаружи — ровно так же, как this. А вот в глобальном коде такая стрелка бросит ReferenceError.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Стрелка — не конструктор. У неё нет механики создания объектов и нет свойства prototype, поэтому new бросает TypeError сразу, даже не заглянув в тело функции.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Поле класса вычисляется отдельно для каждого экземпляра, и this внутри него — сам экземпляр. Стрелка запоминает его навсегда, поэтому такой метод можно оторвать от объекта и передать куда угодно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Выбор почти всегда сводится к одному вопросу: нужен ли этой функции собственный 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 исчезла, а результат остался прежним.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Колбэк для map заменяем стрелкой: она берёт this из метода intro, где this — это team. Промежуточная переменная становится не нужна.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Обратите внимание: сам intro остался обычным методом. Сделай мы стрелкой и его — this перестал бы указывать на team, и никакой колбэк уже не помог бы.

Предскажите this

Не запуская код, скажите, что выведет последняя строка и почему значения различаются.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Выведет false true.

ping — стрелка, лежащая прямо в литерале. Её this взят из кода вокруг объекта (модуль или глобальная область), а не из вызова gadget.ping(), поэтому сравнение с gadget даёт false.

echo — обычный метод, и при вызове gadget.echo() его this равен gadget. Стрелка inner создана внутри этого вызова и наследует тот же this, поэтому получается true.