Menu
Русский

Ключевое слово this в JavaScript: привязка и ловушки

Разбираемся, как на самом деле работает this в JavaScript: четыре правила привязки, почему стрелочные функции ведут себя иначе и как не наступить на классические грабли с this is undefined.

this определяется в момент вызова

Большая часть путаницы вокруг this в JavaScript растёт из одного ложного допущения: будто this зависит от того, где функция объявлена. Нет. this зависит от того, как функцию вызвали.

Посмотрите, как одна и та же функция ведёт себя при двух разных вызовах:

index.js
Output
Click Run to see the output here.

Одна и та же функция — разный this. В первом вызове перед точкой стоит user, поэтому this равен user. Во втором вызове перед функцией нет ничего, и this оказывается undefined. Сама функция не изменилась — изменилось место вызова.

Запомните эту мысль: чтобы понять, чему равен this внутри функции javascript, смотрите не на объявление, а на вызов.

Четыре правила привязки this

Привязка this в javascript определяется одним из четырёх правил. Практически любой вопрос про this сводится к тому, чтобы понять, какое именно из них сработало.

1. Вызов как метод: obj.fn()

Если функция вызывается как свойство объекта, this — это сам объект:

index.js
Output
Click Run to see the output here.

То, что стоит перед точкой, и становится this. Вот и вся магия.

2. Обычный вызов: fn()

Если функция вызывается без объекта перед ней, то this будет undefined в strict mode (а он автоматически включается в модулях и классах) или глобальным объектом в нестрогом режиме:

index.js
Output
Click Run to see the output here.

Именно отсюда берутся ошибки вида «this is undefined». Стоит вытащить метод из объекта — и вызов метода превращается в обычный вызов функции:

index.js
Output
Click Run to see the output here.

Нет counter. перед вызовом — нет и привязки. Функция попросту не помнит объект, из которого её достали.

3. Явная привязка: .call(), .apply(), .bind()

this можно насильно указать вручную — в какое угодно значение:

index.js
Output
Click Run to see the output here.

.call и .apply вызывают функцию сразу же и отличаются только тем, как передаются аргументы. А вот .bind возвращает новую функцию, в которой this зафиксирован намертво — удобно для колбэков.

4. Вызов через new: new Fn()

Когда функция вызывается через new, создаётся новый объект, и именно он становится this:

index.js
Output
Click Run to see the output here.

Классы используют этот же механизм под капотом. Мы познакомимся с ними в одной из следующих глав.

this в стрелочной функции

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

index.js
Output
Click Run to see the output here.

Стрелочная функция на верхнем уровне модуля захватывает this самого модуля, а это undefined. И хотя мы вызываем её как user.arrow(), стрелка наотрез отказывается перепривязываться.

На первый взгляд похоже на баг, но в этом и весь смысл. this в стрелочной функции особенно удобен внутри методов — когда нужно сохранить внешний this:

index.js
Output
Click Run to see the output here.

Стрелочная функция внутри setInterval наследует this от start, а start был вызван как timer.start(). Поэтому this.seconds работает как надо. Обычная function на этом месте получила бы собственный this (тот, который подсунет setInterval), и всё бы сломалось.

Правило на практике: для колбэков внутри методов берите стрелочные функции, а сами методы объявляйте как обычные функции.

Классическая ловушка с this в колбэке

Это самый частый способ потерять this в javascript. Передаёте метод как колбэк — и привязка слетает:

index.js
Output
Click Run to see the output here.

setTimeout вызывает функцию как обычную, а не как c.increment(). Исправить это можно тремя способами:

index.js
Output
Click Run to see the output here.

Все три варианта рабочие. Но обёртка со стрелочной функцией обычно читается понятнее всего.

this на верхнем уровне

Значение this на верхнем уровне зависит от того, где выполняется код:

  • Скрипт в браузере (не модуль): this — это window.
  • ES-модуль (сюда же относится большинство современного кода, собранного бандлером): this равен undefined.
  • CommonJS-модуль в Node.js: this — это module.exports.

Если нужна надёжная ссылка на глобальный объект в любом окружении, используйте globalThis:

index.js
Output
Click Run to see the output here.

На практике лучше не полагаться на this верхнего уровня. Если вам действительно нужен глобальный объект — берите globalThis. Во всех остальных случаях передавайте значения явно.

Быстрое дерево решений

Если вы смотрите на this и не понимаете, чему он будет равен, пройдитесь по этому списку:

  1. Это стрелочная функция? Тогда this берётся из внешней области видимости. Как её вызвали — неважно.
  2. Функцию вызвали через new? Тогда this — это новый объект.
  3. Её вызвали через .call, .apply или как связанную (bound) функцию? Тогда this — то, что передали.
  4. Вызов вида obj.method()? Тогда this — это obj.
  5. Обычный вызов fn()? Тогда в strict mode this будет undefined.

Эта лесенка — именно в таком порядке — покрывает все случаи.

Дальше: функции высшего порядка

Теперь, когда this больше не загадка, можно переходить к приёму, за счёт которого JavaScript такой выразительный — к передаче функций как обычных значений. В следующей главе разберём функции высшего порядка: те, что принимают другие функции или возвращают их. Именно на них держатся методы массивов, обработчики событий и, по сути, весь живой JavaScript-код.

Часто задаваемые вопросы

На что указывает this в JavaScript?

this указывает на объект, у которого вызвана функция, а вовсе не туда, где она была объявлена. В вызове user.greet() this — это user. А если просто написать greet(), то в strict mode this будет undefined (в нестрогом режиме — глобальный объект). Главное правило: смотрим на место вызова, а не на место объявления.

Почему внутри функции this равен undefined?

Скорее всего, вы оторвали метод от объекта и вызвали его отдельно — или передали как колбэк. В коде const fn = user.greet; fn(); привязка теряется: слева от точки в месте вызова нет никакого объекта. Лечится через .bind(user), обёртку со стрелочной функцией или просто вызовом user.greet().

Чем this в стрелочных функциях отличается от обычных?

У стрелочных функций нет собственного this — они берут его из окружающей области видимости в момент объявления. Поэтому их удобно использовать в колбэках внутри методов, когда нужно сохранить внешний this. И ещё важный момент: .call(), .apply() и .bind() не могут подменить this у стрелочной функции.

Чему равен this на верхнем уровне скрипта?

В обычном скрипте в браузере this на верхнем уровне — это объект window. В ES-модуле — undefined. В CommonJS-модулях Node.js — module.exports. Универсальная ссылка на глобальный объект, работающая везде, — это globalThis.

Учитесь программировать с Coddy

НАЧАТЬ