Что такое this и откуда он берётся
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
У функции есть текст, а у this — нет: его значение не написано внутри функции, а подставляется в момент вызова. Разберёмся, кто именно его подставляет и почему один и тот же метод то работает, то падает.
Во многих языках this — это «сам объект», и он известен сразу, по тексту класса. В JavaScript иначе: обычная функция не помнит, где её написали. this внутри неё — пустое место, которое заполняется в момент вызова. Со стороны объекта мы это уже видели в статье «Методы и this»; здесь смотрим со стороны функции.
Функция одна — а this два разных. Поэтому вопрос «чему равен this в этой функции» без вызова смысла не имеет. Правильный вопрос — «как её позвали».
Удобно думать о this как о скрытом первом параметре: его не пишут в скобках, но он приходит вместе с вызовом, как и обычные аргументы.
Самый частый случай — вызов метода: obj.method(). Правило простое: this — это то, что стоит слева от точки в выражении вызова. Не там, где объект объявлен, а именно здесь и сейчас.
Выручалочка: глазами найди точку перед скобками вызова. Что слева от неё — то и в this. Нет точки — нет и объекта.
Если точек в цепочке несколько, берётся ближайший слева объект — всё, что было до него, на this не влияет. Квадратные скобки работают так же: user["greet"]() даст тот же this, что и user.greet().
А если слева от скобок ничего нет — просто fn()? Тогда объекта-владельца у вызова нет, и подставлять в this нечего. В строгом режиме язык честно оставляет там undefined.
Сама по себе такая функция не падает — ошибка приходит на первом же обращении к свойству: у undefined нет ни name, ни чего-либо ещё. Знакомое сообщение «Cannot read properties of undefined» почти всегда означает именно потерянный this.
Строгий режим включается директивой "use strict" в начале файла или тела функции; код в ES-модулях и внутри class строгий всегда. Для this разница ровно одна, зато заметная: строгий режим оставляет undefined, а нестрогий молча подменяет его на глобальный объект.
Практический вывод: современный код почти весь строгий, и это хорошо. Потерянный this даёт быстрый TypeError в той же строке, где ошиблись, вместо тихой записи мусора в глобальный объект — такую находят через час отладки.
Нестрогий вызов не только подменяет пустой контекст, но и оборачивает примитивы в объекты. Задать this вручную позволяет метод call — ему посвящена статья «call, apply и bind», здесь он нужен только для демонстрации.
В строгом режиме такого не происходит: там в this попадает ровно то, что передали, — число остаётся числом.
Глобальный объект — тот, где живут console, setTimeout и глобальные переменные скрипта. В браузере он зовётся window, в Node.js — global, а надёжное имя, работающее везде, — globalThis.
На верхнем уровне обычного скрипта this и есть этот объект; на верхнем уровне ES-модуля — undefined. Именно из-за подмены в нестрогом режиме функция, вызванная без объекта, способна незаметно дописывать свойства в глобальное пространство.
Функция думала, что работает со «своим» объектом, а на деле дважды дописала свойство в общий для всех globalThis. В строгом коде та же попытка обвалилась бы сразу — и это дешевле.
Самая частая ошибка новичка — «оторвать» метод от объекта: положить его в переменную или передать колбэком в setTimeout, forEach, обработчик события. Точка исчезла — исчез и this. Каждому такому случаю и всем трём лечениям посвящена следующая статья — «Потеря контекста и как её чинить».
Функцию никто не испортил — её просто позвали иначе. Собрать всё вместе помогает шпаргалка по видам вызова:
| Как вызвали | Чему равен this |
|---|---|
| obj.method() | obj — то, что слева от точки |
| fn(), строгий режим | undefined |
| fn(), нестрогий режим | globalThis |
| new Fn() | новый создаваемый объект |
| fn.call(x), fn.apply(x) | x — то, что передали явно |
Контекст можно приклеить к функции заранее методом bind: он возвращает новую функцию, у которой this зафиксирован навсегда.
Есть и другие способы — методы 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». Саму функцию менять нельзя.
Раз this задаётся вызовом, достаточно положить одну и ту же функцию в оба объекта и позвать её через точку.
Функция не копируется и не меняется — в объекты попадает ссылка на неё же. Разным получается только контекст: orc.hit() подставляет orc, elf.hit() — elf.
Счётчик, потерявший себя
Метод inc увеличивает counter.value и печатает результат. Через counter.inc() всё работает, а через переменную go — нет. Объясни почему и почини вызов так, чтобы вывелось 2.
В переменную go попала сама функция, а не пара «объект + функция». При вызове go() точки слева нет, поэтому this — либо undefined (строгий режим, будет TypeError), либо globalThis (нестрогий: тогда счётчик увеличивает постороннее свойство и печатает NaN).
Самое простое решение — вернуть точку:
Если функцию всё же нужно передать дальше, привяжи контекст: