Что такое this и откуда он берётся

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

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

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

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

Во многих языках this — это «сам объект», и он известен сразу, по тексту класса. В JavaScript иначе: обычная функция не помнит, где её написали. this внутри неё — пустое место, которое заполняется в момент вызова. Со стороны объекта мы это уже видели в статье «Методы и this»; здесь смотрим со стороны функции.

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

Функция одна — а this два разных. Поэтому вопрос «чему равен this в этой функции» без вызова смысла не имеет. Правильный вопрос — «как её позвали».

Удобно думать о this как о скрытом первом параметре: его не пишут в скобках, но он приходит вместе с вызовом, как и обычные аргументы.

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

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

Выручалочка: глазами найди точку перед скобками вызова. Что слева от неё — то и в this. Нет точки — нет и объекта.

Если точек в цепочке несколько, берётся ближайший слева объект — всё, что было до него, на this не влияет. Квадратные скобки работают так же: user["greet"]() даст тот же this, что и user.greet().

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

А если слева от скобок ничего нет — просто fn()? Тогда объекта-владельца у вызова нет, и подставлять в this нечего. В строгом режиме язык честно оставляет там undefined.

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

Сама по себе такая функция не падает — ошибка приходит на первом же обращении к свойству: у undefined нет ни name, ни чего-либо ещё. Знакомое сообщение «Cannot read properties of undefined» почти всегда означает именно потерянный this.

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

Строгий режим включается директивой "use strict" в начале файла или тела функции; код в ES-модулях и внутри class строгий всегда. Для this разница ровно одна, зато заметная: строгий режим оставляет undefined, а нестрогий молча подменяет его на глобальный объект.

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

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

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

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

В строгом режиме такого не происходит: там в this попадает ровно то, что передали, — число остаётся числом.

Глобальный объект — тот, где живут console, setTimeout и глобальные переменные скрипта. В браузере он зовётся window, в Node.js — global, а надёжное имя, работающее везде, — globalThis.

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

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

Функция думала, что работает со «своим» объектом, а на деле дважды дописала свойство в общий для всех globalThis. В строгом коде та же попытка обвалилась бы сразу — и это дешевле.

Самая частая ошибка новичка — «оторвать» метод от объекта: положить его в переменную или передать колбэком в setTimeout, forEach, обработчик события. Точка исчезла — исчез и this. Каждому такому случаю и всем трём лечениям посвящена следующая статья — «Потеря контекста и как её чинить».

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

Функцию никто не испортил — её просто позвали иначе. Собрать всё вместе помогает шпаргалка по видам вызова:

Как вызвалиЧему равен this
obj.method()obj — то, что слева от точки
fn(), строгий режимundefined
fn(), нестрогий режимglobalThis
new Fn()новый создаваемый объект
fn.call(x), fn.apply(x)x — то, что передали явно

Контекст можно приклеить к функции заранее методом bind: он возвращает новую функцию, у которой this зафиксирован навсегда.

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

Есть и другие способы — методы call и apply или обёртка-стрелка, у которой своего this нет вовсе: о ней статья «Стрелочные функции и this».

  • this — не часть текста функции, а скрытый параметр, который подставляется в момент вызова.
  • При вызове obj.method() в this попадает объект слева от точки; из цепочки берётся ближайший слева.
  • Вызов fn() без объекта: в строгом режиме this равен undefined, в нестрогом подменяется на globalThis.
  • Ошибка «Cannot read properties of undefined» почти всегда означает, что метод оторвали от объекта.
  • globalThis — единое имя глобального объекта: window в браузере, global в Node.js.

1/3Что выведет этот код? const a = { n: 1, get() { return this.n; } }; const b = { n: 2, get: a.get }; console.log(b.get());

2/3Обычная функция в строгом режиме вызвана как fn(), без объекта. Чему равен this?

3/3Когда определяется значение this внутри обычной функции?

Один метод — два героя

Функция attack написана отдельно и ни к какому объекту не привязана. Сделай так, чтобы она вывела «Орк бьёт на 7», а затем «Эльф бьёт на 4». Саму функцию менять нельзя.

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

Раз this задаётся вызовом, достаточно положить одну и ту же функцию в оба объекта и позвать её через точку.

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

Функция не копируется и не меняется — в объекты попадает ссылка на неё же. Разным получается только контекст: orc.hit() подставляет orc, elf.hit() — elf.

Счётчик, потерявший себя

Метод inc увеличивает counter.value и печатает результат. Через counter.inc() всё работает, а через переменную go — нет. Объясни почему и почини вызов так, чтобы вывелось 2.

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

В переменную go попала сама функция, а не пара «объект + функция». При вызове go() точки слева нет, поэтому this — либо undefined (строгий режим, будет TypeError), либо globalThis (нестрогий: тогда счётчик увеличивает постороннее свойство и печатает NaN).

Самое простое решение — вернуть точку:

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

Если функцию всё же нужно передать дальше, привяжи контекст:

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