Menu

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

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

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

На практике лучше не полагаться на 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 programming languages illustration

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

НАЧАТЬ