Курс по ключевому слову this в JavaScript

Курс объясняет, как работает контекст выполнения и что именно означает this в разных ситуациях JavaScript. Разберём основные правила привязки this, типичные ошибки и практические способы управления контекстом.

1. Что такое this и контекст выполнения

Что такое this и контекст выполнения

Ключевое слово this — одна из самых частых причин ошибок и непонимания в JavaScript. Главная причина в том, что this не привязано к функции навсегда: его значение зависит от того, как именно была вызвана функция.

В этой статье мы разберём базовые понятия:

  • что такое this простыми словами
  • что такое контекст выполнения (execution context)
  • когда и как JavaScript определяет значение this
  • почему this в одном и том же коде может быть разным
  • Что такое this

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

    Важно: this — не переменная, которую вы объявляете сами. Это специальное значение, которое движок JavaScript подставляет при вызове.

    Два ключевых факта:

  • Значение this определяется в момент вызова функции, а не в момент её объявления.
  • Значение this зависит от способа вызова (call-site).
  • Официальная справка: MDN: this

    Контекст выполнения

    Когда JavaScript выполняет код, он делает это не “в вакууме”, а внутри структуры, которую обычно называют контекстом выполнения.

    Контекст выполнения можно понимать как “пакет параметров”, который движок создаёт, чтобы выполнить определённый фрагмент кода.

    Обычно в контекст выполнения входят:

  • где хранить локальные переменные и параметры функции
  • как работать с областями видимости (scope)
  • какое значение будет у this
  • Контексты выполнения создаются, например:

  • при запуске скрипта (глобальный контекст)
  • при каждом вызове функции (контекст функции)
  • !Контексты выполнения добавляются в стек при вызове функций и убираются при завершении

    Глобальный контекст и this

    В самом верхнем уровне выполнения (в глобальном контексте) значение this зависит от среды и режима.

    Браузер

    В обычном скрипте (не модуль) в браузере глобальный this обычно указывает на window.

    Node.js

    В Node.js поведение отличается: на верхнем уровне файла this не равен глобальному объекту.

    Чтобы получить универсальную ссылку на глобальный объект, используйте globalThis:

  • стандарт: MDN: globalThis
  • Главное правило: this зависит от способа вызова

    Один и тот же код функции может получить разный this.

    Сравните:

    И:

    Снаружи функция похожа, но “место вызова” разное — значит, и this будет разным.

    Типичные способы вызова и что будет this

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

    Вызов как обычной функции

    Когда функция вызывается “просто по имени” (или через переменную, в которой лежит функция), this зависит от режима:

  • в нестрогом режиме this обычно становится глобальным объектом (в браузере — window)
  • в строгом режиме this будет undefined
  • Строгий режим: MDN: strict mode

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

    Вызов как метода объекта

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

    Ключевая идея: важна не “где функция объявлена”, а “как она вызвана”.

    Потеря this при “отрыве” метода

    Очень частая ошибка: метод вытащили в переменную, а потом вызвали как обычную функцию.

    Здесь fn() — обычный вызов функции. Значит, правило “метода” больше не работает.

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

    Стрелочные функции (() => {}) ведут себя иначе: у них нет собственного this.

    Они берут this из внешнего окружения (часто говорят: лексически захватывают this).

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

    Итоги

  • this — это значение, которое движок подставляет при вызове функции.
  • this определяется в момент вызова и зависит от формы вызова.
  • Контекст выполнения — это “рамка выполнения” кода, где, среди прочего, хранится привязка this.
  • В строгом режиме обычный вызов функции даёт this === undefined.
  • Вызов как метода (obj.method()) даёт this === obj.
  • У стрелочных функций нет собственного this: они берут его снаружи.
  • В следующей статье мы систематизируем правила привязки this и разберём больше практических кейсов, где this “ломается” и как это исправлять.

    2. Правила привязки this: по умолчанию, неявная, явная, new

    Правила привязки this: по умолчанию, неявная, явная, new

    В предыдущей статье мы закрепили главный принцип: значение this зависит не от того, где функция написана, а от того, как она вызвана (call-site).

    Теперь систематизируем это в виде четырёх основных правил привязки this, которые покрывают большинство ситуаций в JavaScript:

  • привязка по умолчанию (default binding)
  • неявная привязка (implicit binding)
  • явная привязка (explicit binding)
  • привязка через new (new binding)
  • Эти правила важно знать не как «теорию», а как инструмент диагностики: увидели вызов функции — определили, по какому правилу вычислится this.

    !Схема четырёх правил привязки this и их приоритета

    Привязка по умолчанию

    Привязка по умолчанию срабатывает, когда функция вызывается как обычная функция, без объекта слева от точки и без call/apply/bind/new.

    Что будет в this:

  • В строгом режиме ('use strict') this === undefined.
  • В нестрогом режиме this обычно становится глобальным объектом (в браузере — window).
  • Почему это важно:

  • В строгом режиме вы быстрее обнаружите ошибку, когда метод «потерял» объект и превратился в обычный вызов.
  • В нестрогом режиме ошибка может быть тихой: вместо падения вы внезапно начнёте писать свойства в window.
  • Связанный стандартный глобальный объект, который работает и в браузере, и в Node.js: globalThis.

    Неявная привязка

    Неявная привязка срабатывает, когда функция вызывается как метод объекта: obj.method().

    Правило: this будет ссылаться на объект слева от точки в момент вызова.

    Ключевая деталь: важен именно вызов.

    Цепочки свойств

    Если у вас несколько точек, всё равно берётся объект непосредственно слева от последней точки.

    Потеря неявной привязки

    Неявная привязка легко «ломается», если вы передаёте метод как значение (отрываете от объекта).

    Здесь fn() — это уже обычный вызов, поэтому включается привязка по умолчанию.

    Частые места, где происходит «отрыв»:

  • колбэки (setTimeout(user.say, 0))
  • обработчики событий (если вы передали метод напрямую)
  • деструктуризация (const { say } = user; say())
  • Явная привязка

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

  • call — вызывает функцию, передавая this и аргументы списком
  • apply — вызывает функцию, передавая this и аргументы массивом
  • bindне вызывает, а возвращает новую функцию с «привязанным» this
  • Документация:

  • MDN: Function.prototype.call
  • MDN: Function.prototype.apply
  • MDN: Function.prototype.bind
  • call

    apply

    Здесь this не используется, поэтому часто передают null.

    bind

    bind полезен, когда нужно передать функцию куда-то и не потерять this.

    Практическое отличие:

  • call/applyсразу вызывают функцию
  • bindвозвращает новую функцию, которую можно вызвать позже
  • Привязка через new

    Когда функция вызывается с new, this внутри функции будет ссылаться на новый объект, который создаёт JavaScript.

    Документация:

  • MDN: Оператор new
  • Пример с «функцией-конструктором»:

    Что происходит при new User('Аня') на концептуальном уровне:

  • создаётся новый пустой объект
  • этот объект становится this внутри User
  • функция выполняет тело и обычно записывает поля в this
  • возвращается созданный объект (если вы явно не вернули другой объект)
  • Приоритет правил (кто «побеждает»)

    Если кажется, что «подходят сразу несколько правил», используйте порядок приоритета:

    | Правило | Пример вызова | Приоритет | |---|---|---| | Привязка через new | new Fn() | самый высокий | | Явная привязка | fn.call(obj), fn.apply(obj), fn.bind(obj) | высокий | | Неявная привязка | obj.fn() | ниже | | По умолчанию | fn() | самый низкий |

    Короткая формула для запоминания: new > call/apply/bind > obj.method() > fn().

    Пример конфликта: implicit vs explicit

    Хотя вызов выглядит как «метод объекта b», явная привязка через call(a) имеет более высокий приоритет.

    Важная особенность: new и bind

    С bind есть тонкость: «привязанный this» работает для обычных вызовов, но при использовании new создаётся новый объект, и он становится this.

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

    Итоги

  • Есть четыре базовых правила привязки this: по умолчанию, неявная, явная, new.
  • Чтобы понять this, смотрите на место вызова.
  • Приоритет правил: new > call/apply/bind > obj.method() > fn().
  • Самые частые ошибки связаны с потерей неявной привязки при передаче метода как колбэка; обычно решается bind (или другими подходами, которые мы будем разбирать дальше).
  • 3. this в методах объектов и цепочках вызовов

    this в методах объектов и цепочках вызовов

    В предыдущих статьях курса мы зафиксировали два главных ориентира:

  • this определяется в момент вызова функции, а не в момент её объявления
  • для понимания this нужно смотреть на форму вызова и применять правила привязки: по умолчанию, неявная, явная, через new
  • В этой статье разберём практическую зону, где чаще всего случаются ошибки:

  • this в методах объектов
  • this в цепочках свойств (a.b.c()) и цепочках вызовов (obj.set().save())
  • случаи, когда цепочка “выглядит как метод”, но this теряется
  • !Схема показывает, что в цепочке a.child.say() значение this берётся из объекта child, который стоит слева от последней точки

    this в методе объекта

    Когда функция вызывается как метод, срабатывает неявная привязка.

    Правило: в вызове obj.method() значение this внутри method будет равно obj.

    Здесь важно не то, что функция лежит внутри объекта, а то, что она вызвана в форме user.say().

    this в цепочках свойств

    Цепочка вида a.b.c() читается так:

  • сначала берём значение a.b
  • потом у результата берём свойство c
  • потом вызываем его как функцию
  • Ключевое правило: this берётся из объекта, который стоит слева от последней точки.

    Хотя вся цепочка начинается с a, this будет равен a.child, потому что вызов выглядит как a.child.say() и объект слева от последней точки это a.child.

    Пример с одинаковым методом в разных объектах

    Одна и та же функция может стать методом разных объектов, и this будет зависеть от вызова.

    Как цепочки “ломают” this

    Частая ловушка: код визуально похож на “вызов метода”, но на самом деле метод сначала отрывается от объекта и становится обычной функцией. Тогда включается привязка по умолчанию (в строгом режиме это undefined).

    Отрыв через присваивание

    fn() вызывается без объекта слева от точки, поэтому this не будет user.

    Отрыв через деструктуризацию

    Деструктуризация забрала “голую” функцию. Вызов стал обычным.

    Отрыв в колбэках

    setTimeout вызовет переданную функцию сам. Она будет вызвана не как user.say(), а как обычная функция.

    Неочевидный случай: метод вызывается, но this другой

    Иногда функция всё ещё вызывается “как метод”, но объект слева от точки стал другим.

    Функция та же, но вызов теперь wrapper.fn(), значит this === wrapper.

    Цепочки вызовов и паттерн return this

    Цепочки вызовов часто встречаются в “флюент” API: obj.setX().setY().save().

    Чтобы цепочка работала, методы обычно возвращают this.

    Почему this здесь стабилен:

  • каждый метод вызывается как builder.setX(...), ... .setY(...), ... .print()
  • то есть каждый раз выполняется форма вызова “как метод”, и this берётся из объекта слева от точки
  • Важное замечание про стрелочные функции

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

    Если вам нужен this, используйте обычный метод: say() { ... }.

    this в геттерах и сеттерах

    Геттеры и сеттеры это функции, которые вызываются при доступе к свойству. Внутри них this обычно указывает на объект, через который происходит доступ.

    Здесь доступ идёт через book.title, поэтому this внутри геттера и сеттера будет равен book.

    Документация:

  • MDN: get
  • MDN: set
  • Опциональная цепочка ?. и this

    Опциональная цепочка (?.) позволяет безопасно обращаться к свойствам и вызывать методы, если объект может быть null или undefined.

    Документация:

  • MDN: Optional chaining (?.)
  • Если объект существует, то вызов вида obj?.method() сохраняет “методную” форму вызова.

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

    Опциональная цепочка делает вызов безопаснее с точки зрения null/undefined, но не “чинит” потерю контекста.

    Итоги

  • Вызов obj.method() даёт this === obj за счёт неявной привязки.
  • В цепочках a.b.c() значение this определяется объектом слева от последней точки.
  • this часто теряется при “отрыве” метода: присваивание в переменную, деструктуризация, передача как колбэка.
  • Для цепочек вызовов (obj.set().save()) методы обычно возвращают this.
  • Стрелочные функции не подходят для методов, если внутри нужен this.
  • ?. помогает избежать ошибок при null/undefined, но не восстанавливает this, если метод уже был оторван.
  • 4. Стрелочные функции и лексический this

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

    Стрелочные функции (() => {}) — один из самых удобных инструментов JavaScript, но именно из-за них многие впервые сталкиваются с фразой лексический this.

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

    В этой статье разберём:

  • что значит лексический this
  • как стрелка “берёт” this из внешнего кода
  • почему call/apply/bind почти не влияют на this стрелки
  • где стрелки полезны (колбэки, вложенные функции)
  • где стрелки вредны (методы объектов, обработчики событий)
  • Справка:

  • MDN: Arrow function expressions
  • MDN: this
  • !Сравнение обычной функции и стрелочной по поведению this

    Главная идея: у стрелки нет собственного this

    Обычная функция получает this при вызове по правилам привязки:

  • fn() → привязка по умолчанию
  • obj.fn() → неявная привязка
  • fn.call(obj) → явная привязка
  • new Fn() → привязка через new
  • Стрелочная функция работает иначе:

  • она не создаёт новый this
  • она берёт this из ближайшего внешнего контекста, где this существует
  • Это и называют лексическим this: значение this определяется не вызовом стрелки, а её местом создания в коде.

    Базовое сравнение: обычная функция против стрелки

    Посмотрим на типичный кейс “вложенная функция внутри метода”.

    Обычная функция: this теряется

    Почему так:

  • setTimeout вызывает переданную функцию как обычную (fn())
  • значит, включается привязка по умолчанию
  • в строгом режиме this становится undefined
  • Этот пример напрямую связан с тем, что мы обсуждали ранее: “отрыв” метода и колбэки легко ломают неявную привязку.

    Стрелка: this берётся из метода

    Что произошло:

  • greetLater вызван как метод user.greetLater(), значит this === user
  • стрелка создана внутри greetLater и захватила этот this
  • когда setTimeout вызвал стрелку, она не стала вычислять this заново
  • Почему call/apply/bind не “перебивают” this у стрелки

    У обычной функции можно “переопределить” this через call/apply/bind.

    У стрелки это почти бессмысленно: стрелка игнорирует попытки привязать ей this, потому что собственного this у неё нет.

    Это не “магия”, а следствие правила: стрелка использует this внешнего окружения, а не this, который кто-то пытается подставить при вызове.

    Важно:

  • bind для стрелки может быть полезен как способ зафиксировать аргументы или просто создать новую функцию
  • но значение this у стрелки от bind не изменится
  • Почему стрелочные функции плохо подходят для методов объекта

    Очень частая ошибка: сделать метод стрелкой и ожидать, что this будет объектом.

    Причина:

  • стрелка создана в момент создания объекта
  • её this берётся из внешнего контекста (для скрипта в строгом режиме это обычно undefined)
  • вызов user.say() не включает правило неявной привязки, потому что стрелка не принимает this от вызова
  • Вывод:

  • если внутри нужен this, пишите обычный метод: say() { ... }
  • стрелку как метод стоит использовать только если this внутри вообще не нужен
  • Стрелка как инструмент “сохранить this” во вложенных функциях

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

    Примеры:

  • колбэки таймеров (setTimeout, setInterval)
  • итераторы массивов (map, forEach, filter)
  • обработка промисов (then, catch, finally)
  • Пример с массивом

    Если бы внутри forEach была обычная функция, this.title не работал бы без дополнительных приёмов.

    Альтернативы стрелкам: bind и переменная self

    Стрелка — не единственный способ сохранить this.

    bind

    Здесь мы явно привязали this у обычной функции к this метода.

    self (или that)

    Это старый, но всё ещё встречающийся стиль.

    Минус подхода: вы вручную переносите ссылку, и код становится менее выразительным.

    Важные ограничения стрелочных функций, связанные с this

    Эти факты помогают быстро понять, где стрелки уместны, а где нет.

    Стрелку нельзя использовать с new

    Стрелочные функции не являются конструкторами.

    Это логично: new — правило привязки, которое создаёт новый this, а у стрелки “собственного this” нет.

    this в стрелке “фиксируется” на момент создания

    Стрелка не “подстраивается” под разные вызовы.

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

    Практическое резюме

  • Стрелочная функция не имеет собственного this.
  • this стрелки лексический: берётся из внешнего контекста в момент создания.
  • call/apply/bind не меняют this стрелки.
  • Стрелки полезны во вложенных колбэках, чтобы не терять this внешнего метода.
  • Стрелки почти всегда плохая идея для методов объектов, если внутри нужен this.
  • В следующих материалах курса удобно опираться на это правило как на “пятый особый случай”: когда вы видите стрелку, вы сразу знаете, что правила привязки из прошлой статьи к её this неприменимы.

    5. call, apply, bind: управление this на практике

    call, apply, bind: управление this на практике

    В предыдущих статьях мы разобрали, что this определяется местом вызова (call-site) и подчиняется правилам привязки: по умолчанию, неявная, явная и через new. Также мы увидели, что стрелочные функции живут по особому правилу: у них нет собственного this, он лексический.

    В этой статье мы сфокусируемся на явной привязке this — то есть на инструментах, которые позволяют управлять контекстом напрямую:

  • Function.prototype.call
  • Function.prototype.apply
  • Function.prototype.bind
  • Справка на MDN:

  • Function.prototype.call
  • Function.prototype.apply
  • Function.prototype.bind
  • !Схема различий между call/apply (вызов сразу) и bind (создаёт новую функцию)

    Когда вообще нужны call, apply, bind

    Главная практическая причина — потеря контекста при передаче метода как значения.

    setTimeout вызовет переданную функцию как обычную (fn()), а значит сработает привязка this по умолчанию. Исправления часто делаются через bind или через обёртку.

    Быстрое сравнение: call vs apply vs bind

    | Метод | Делает что | Когда исполняется функция | Как передаются аргументы | Что возвращает | |---|---|---|---|---| | call | вызывает функцию с указанным this | сразу | списком | результат вызова | | apply | вызывает функцию с указанным this | сразу | массивом (или array-like) | результат вызова | | bind | создаёт новую функцию с “привязанным” this | не сразу | можно передать часть аргументов | новую функцию |

    Ключевая идея:

  • call/apply полезны, когда нужно вызвать прямо сейчас.
  • bind полезен, когда нужно передать функцию куда-то и не потерять this.
  • call: явная привязка this и аргументы списком

    Сигнатура (упрощённо): fn.call(thisArg, a, b, c).

    Пример: одна функция, разные объекты.

    Что важно:

  • say не “принадлежит” объекту, но call позволяет временно сделать так, будто вызов был методом.
  • значение this задаётся первым аргументом call.
  • Заимствование методов

    call часто используют для заимствования метода другого объекта.

    apply: то же самое, но аргументы массивом

    Сигнатура (упрощённо): fn.apply(thisArg, [a, b, c]).

    apply удобен, когда аргументы уже собраны в массив.

    Здесь передаётся null, потому что this внутри sum не используется.

    apply и "массивоподобные" структуры

    Исторически apply применяли, чтобы работать с arguments (в обычных функциях) или с array-like объектами. В современном JavaScript часто удобнее использовать оператор расширения ..., но apply всё ещё полезен, когда требуется именно API в виде массива.

    bind: создать функцию с привязанным this

    bind отличается принципиально: он не вызывает функцию, а возвращает новую.

    Ментальная модель:

  • bind “упаковывает” исходную функцию вместе с выбранным this.
  • после этого вы можете передавать boundSay куда угодно, и this не потеряется.
  • bind и частичное применение аргументов

    bind умеет фиксировать не только this, но и часть аргументов.

    Здесь this не используется, поэтому передали null, а вот первый аргумент lang зафиксировали.

    Что происходит, если thisArg равен null или undefined

    Поведение зависит от режима и от того, обычная ли это функция.

  • В нестрогом режиме некоторые движки могут подставлять глобальный объект вместо null/undefined.
  • В строгом режиме this остаётся тем, что вы передали: null остаётся null, undefined остаётся undefined.
  • Практический вывод: если this не нужен, передавайте null осознанно, но не пишите код, который зависит от того, что движок “подставит глобальный объект”.

    bind и new: кто сильнее

    Из предыдущей статьи про приоритет правил: привязка через new имеет более высокий приоритет, чем явная привязка.

    bind “фиксирует” this для обычных вызовов, но если вызвать связанную функцию через new, this станет новым объектом.

    Это ожидаемо, потому что new создаёт новый объект и назначает его this.

    Почему call/apply/bind не помогают стрелочным функциям

    Из статьи про стрелки: у стрелочной функции нет собственного this, поэтому попытки “прибиндить” контекст почти всегда бессмысленны.

    Вывод:

  • call/apply/bind полезны для обычных функций.
  • если вы видите стрелку и проблема именно в this, чаще всего решение в том, чтобы изменить место создания стрелки или заменить стрелку на обычную функцию.
  • Практические паттерны: как выбирать инструмент

  • Если нужно вызвать сейчас и подставить this, используйте call.
  • Если нужно вызвать сейчас, но аргументы уже в массиве, используйте apply.
  • Если нужно передать функцию дальше (колбэк, обработчик, подписка) и сохранить this, используйте bind.
  • Альтернатива bind: обёртка

    Иногда вместо bind проще сделать обёртку, особенно если нужно контролировать аргументы.

    Здесь this не нужен, потому что мы явно вызываем метод через user.say(...). Но важно понимать: это работает, пока у вас есть доступ к user в замыкании.

    Итоги

  • call(thisArg, ...args) и apply(thisArg, argsArray) вызывают функцию сразу с заданным this.
  • bind(thisArg, ...args) возвращает новую функцию с зафиксированным this и, при желании, частью аргументов.
  • bind полезен против “потери контекста” при передаче методов как колбэков.
  • new имеет приоритет над bind: при вызове через new this станет новым объектом.
  • Стрелочные функции не получают this от call/apply/bind, потому что их this лексический.
  • 6. this в классах, конструкторах и прототипах

    this в классах, конструкторах и прототипах

    К этому моменту курса у нас есть прочная база:

  • this определяется в момент вызова.
  • Есть правила привязки this: по умолчанию, неявная, явная, через new.
  • Стрелочные функции имеют лексический this.
  • call/apply/bind позволяют управлять this у обычных функций.
  • Теперь соберём всё это в одной из самых практичных областей: классы, конструкторы и прототипы.

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

    Полезные ссылки:

  • MDN: class
  • MDN: constructor
  • MDN: Object.prototype
  • MDN: super
  • !Диаграмма, которая связывает классы с прототипами и показывает, откуда берётся this при вызове метода

    Класс как синтаксис над прототипами

    Когда вы пишете класс, JavaScript создаёт:

  • функцию-конструктор (её вызывает new)
  • объект User.prototype, где лежат методы экземпляра
  • Ключевые моменты:

  • в конструкторе наследника нельзя использовать this до super(...)
  • super.say() вызывает метод родителя, но this внутри него будет тем же экземпляром d
  • Ссылка:

  • MDN: extends
  • Статические методы: this указывает на класс

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

    Внутри static-метода:

  • при вызове Utils.version() срабатывает неявная привязка
  • объект слева от точки — это Utils
  • Если вы унаследуетесь, this станет “классом-потомком” при вызове статического метода у потомка.

    Ссылка:

  • MDN: static
  • Приватные поля и методы: this работает так же

    Приватные поля и методы (с #) не меняют правила this. Это только механизм инкапсуляции.

    Ссылка:

  • MDN: Private class fields
  • Прототипы напрямую: то же самое без class

    Чтобы “почувствовать”, что классы — это синтаксис над прототипами, полезно увидеть эквивалент на функциях-конструкторах.

    Здесь всё знакомо:

  • new User('Аня') даёт this как новый объект внутри User
  • u.say() даёт this === u внутри say
  • То есть прототипы отвечают за то, где лежит функция, а this отвечает за то, как функция вызвана.

    Итоги

  • В constructor класса this задаётся правилом new: это новый экземпляр.
  • Методы экземпляра класса обычно лежат в ClassName.prototype, и this в них зависит от формы вызова obj.method().
  • Отрыв метода (const fn = obj.method; fn()) ломает this так же, как и у обычных объектов.
  • Способы стабилизировать this для колбэков: bind в конструкторе, стрелка в поле класса, обёртка в месте вызова.
  • В наследовании super(...) обязателен до использования this в конструкторе потомка, а super.method() сохраняет тот же this (экземпляр потомка).
  • В static-методах this обычно указывает на класс (на объект слева от точки).
  • Эта статья связывает “правила привязки” из начала курса с реальным стилем разработки на классах. Дальше (если вы будете углубляться) удобно смотреть на любые проблемы с классами через тот же алгоритм: найдите место вызова и определите, какое правило привязки this сработало.

    7. Частые ошибки с this и отладка в реальных проектах

    Частые ошибки с this и отладка в реальных проектах

    this ломается не потому, что JavaScript сложный, а потому что мы часто мысленно привязываем this к функции, хотя на самом деле this привязывается к вызову.

    В прошлых статьях курса мы уже разобрали:

  • что this зависит от call-site (места вызова)
  • правила привязки this: по умолчанию, неявная, явная, через new
  • стрелочные функции и их лексический this
  • практику call, apply, bind
  • this в классах, конструкторах и прототипах
  • В этой статье соберём самые частые ошибки с this в реальных проектах и системный подход к отладке.

    !Дерево решений помогает быстро определить, какое правило привязки this сработает

    Как обычно проявляются ошибки с this

    Чаще всего вы увидите одно из двух:

  • TypeError: Cannot read properties of undefined при обращении к this.something
  • «странные данные»: this указывает не на тот объект (например, на window, на DOM-элемент, на другой экземпляр)
  • Полезная привычка: при любом подозрении на this сначала выясняйте в какой форме вызывается функция.

    Ошибка: «оторвали» метод и вызвали как обычную функцию

    Это самая популярная причина потери контекста. Был метод obj.method(), а стал обычный вызов fn().

    Почему так:

  • fn() не имеет объекта слева от точки
  • значит, срабатывает привязка по умолчанию
  • в строгом режиме this === undefined
  • Как чинят в проектах:

  • bind
  • обёртка (часто удобнее, если нужно прокинуть аргументы)
  • Связанные темы курса: неявная привязка и её потеря, а также bind.

    Ошибка: передали метод как колбэк (таймер, промисы, массивы)

    Колбэк почти всегда вызывается «чужим кодом» как обычная функция.

    Таймеры

    Исправления:

  • setTimeout(user.say.bind(user), 0)
  • setTimeout(() => user.say(), 0)
  • Промисы

    Исправления те же: bind или обёртка.

    Ошибка: стрелочная функция как метод объекта

    Стрелка не получает this от вызова obj.method(). Её this берётся из внешнего контекста в момент создания.

    Правильнее:

    Где стрелка уместна: внутри метода как колбэк, чтобы «захватить» this метода.

    Ошибка: обработчики событий и «не тот this»

    В браузере this в обработчике часто указывает на элемент, на котором висит обработчик (если используется обычная функция).

    Частая ошибка: ожидали, что this будет вашим объектом/классом.

    Решения зависят от того, что именно нужно:

  • если нужен DOM-элемент, обычная функция подходит
  • если нужен экземпляр класса, используйте bind или стрелку, созданную в правильном контексте
  • Пример с классом:

    Ошибка: деструктуризация методов

    Деструктуризация выглядит безобидно, но она «отрывает» функцию.

    Решения:

  • не деструктурировать методы, которым нужен this
  • или деструктурировать уже bind-версию: const { say } = { say: user.say.bind(user) }
  • Ошибка: попытка «починить» стрелку через call/apply/bind

    call, apply, bind управляют this только у обычных функций. У стрелки this не переопределяется.

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

  • заменить на обычную функцию
  • или перенести создание стрелки туда, где внешний this правильный
  • Документация: Arrow function expressions

    Ошибка: bind в цикле/рендере и скрытые утечки производительности

    В реальных приложениях (особенно UI) встречается паттерн:

    Проблемы:

  • на каждый вызов bind создаётся новая функция
  • сложно снять обработчик через removeEventListener, потому что нужна та же ссылка на функцию
  • Более управляемые подходы:

  • заранее хранить привязанные функции
  • использовать делегирование событий
  • использовать одну функцию и доставать данные из event/dataset
  • Ключевая идея: bind полезен, но создавать bound-функции нужно осознанно, понимая жизненный цикл.

    Ошибка: методы класса «теряются» при передаче

    Даже если метод написан в классе, правила this те же.

    Практические решения, которые чаще всего используют в проектах:

  • this.say = this.say.bind(this) в constructor
  • поле класса со стрелкой say = () => { ... } (это будет свойство экземпляра)
  • обёртка в месте передачи setTimeout(() => u.say(), 0)
  • Документация: Classes

    Ошибка: путаница strict mode и «глобального this»

    В нестрогом режиме при обычном вызове fn() this может стать глобальным объектом. В строгом режиме он будет undefined.

    Почему это важно в отладке:

  • в строгом режиме ошибка проявится быстрее (упадёте при попытке читать this.name)
  • в нестрогом режиме ошибка может быть тихой: вы случайно записываете свойства в глобальный объект
  • Документация: Strict mode

    Алгоритм отладки this в проекте

    Ниже — рабочий чеклист, который помогает быстро локализовать проблему.

    Шаг: увидеть реальный call-site

    Вставьте минимальную диагностику прямо в проблемное место:

    console.trace() покажет стек вызовов и поможет ответить на вопрос: кто и как вызывает функцию.

    Документация: console.trace

    Шаг: классифицировать по правилам привязки

    Проверьте, не попадает ли ваш случай в один из шаблонов:

  • new Fn()
  • fn.call(x) / fn.apply(x) / fn.bind(x)
  • obj.fn()
  • fn()
  • стрелка () => {} (лексический this)
  • Как только вы выбрали шаблон, значение this становится предсказуемым.

    Шаг: проверить «переупаковки» функции

    В больших кодовых базах this теряется не там, где вы его используете, а там, где функцию передают дальше.

    Типовые места:

  • присваивание const fn = obj.method
  • деструктуризация const { method } = obj
  • передача аргументом someLib(obj.method)
  • подписки/события/таймеры
  • Шаг: поставить брейкпоинт и посмотреть this в отладчике

    В браузерных DevTools и в Node.js (инспектор) можно:

  • поставить брейкпоинт внутри функции
  • посмотреть this в Scope/Watch
  • подняться по стеку и найти исходный call-site
  • Для Node.js полезно знать: Node.js Debugging Guide

    Практические приёмы профилактики

    Явно выбирайте стиль методов

  • если внутри нужен this, используйте обычный метод method() { ... }
  • если вам нужен лексический this внешнего контекста, используйте стрелку
  • Фиксируйте контекст на границах системы

    «Границы» — это места, где ваш код отдаёт функции внешнему миру:

  • события
  • таймеры
  • подписки
  • API библиотек
  • Часто достаточно правила: на границе либо bind, либо обёртка.

    Используйте линтер и типизацию как раннюю сигнализацию

    ESLint умеет ловить часть ошибок с this до запуска.

  • ESLint
  • TypeScript может подсветить сомнительный this через настройки компилятора, например noImplicitThis.

  • TypeScript compiler options: noImplicitThis
  • Итоги

  • Большинство ошибок с this — это либо потеря неявной привязки (оторвали метод), либо неверное ожидание от стрелок.
  • call/apply/bind исправляют this у обычных функций, но почти не помогают стрелкам.
  • В обработчиках событий this может быть DOM-элементом — это не баг, а модель вызова.
  • Лучший способ отладки: зафиксировать this, вывести console.trace(), найти call-site и применить правила привязки.
  • Профилактика в проектах: осознанный выбор между обычными методами и стрелками, привязка на границах, ESLint и TypeScript.