Как изменить контекст (`this`) в JavaScript?

Ответ

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

1. Явный вызов с помощью call(), apply() и bind(): Эти методы доступны у всех функций.

  • func.call(thisArg, arg1, arg2, ...) Вызывает функцию с заданным контекстом thisArg и аргументами, переданными индивидуально.

    function greet(greeting, punctuation) {
      console.log(`${greeting}, ${this.name}${punctuation}`);
    }
    
    const person = { name: 'Alice' };
    greet.call(person, 'Hello', '!'); // Вывод: Hello, Alice!
  • func.apply(thisArg, [argsArray]) Аналогичен call(), но аргументы передаются в виде массива (или array-like объекта).

    const args = ['Hi', '?'];
    greet.apply(person, args); // Вывод: Hi, Alice?

    Полезно, когда количество аргументов неизвестно заранее.

  • func.bind(thisArg, arg1, arg2, ...) Создает новую функцию, которая при вызове будет иметь заданный контекст this. Аргументы, переданные в bind, будут подставлены первыми.

    const boundGreet = greet.bind(person, 'Hey');
    boundGreet('...'); // Вывод: Hey, Alice...
    // Контекст person зафиксирован

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

2. Особенности стрелочных функций: Стрелочные функции не имеют своего this. Они лексически захватывают значение this из окружающей их области видимости. Методы call(), apply(), bind() не работают для изменения их контекста.

const obj = {
  value: 'My Value',
  regularFunc: function() {
    console.log(this.value); // 'My Value' (this = obj)
  },
  arrowFunc: () => {
    console.log(this.value); // undefined (this берется из внешней области, например, global)
  }
};

const newContext = { value: 'New Value' };
obj.regularFunc.call(newContext); // 'New Value' (контекст изменен)
obj.arrowFunc.call(newContext);   // undefined (контекст НЕ изменен)

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

function Car(model) {
  this.model = model; // this указывает на новый экземпляр
}
const myCar = new Car('Tesla');
console.log(myCar.model); // 'Tesla'

Итог: Для явного управления контекстом используйте call/apply для немедленного вызова или bind для создания новой функции с фиксированным контекстом. Выбирайте обычные функции, когда нужен динамический this, и стрелочные — когда нужно сохранить контекст родительской функции.

Ответ 18+ 🔞

Давай разберем эту тему про this, а то у многих мозг набекрень встаёт, как только речь заходит о контексте. Ситуация, блядь, пиздопроебибна, но на самом деле всё логично, если понять принцип.

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

1. Прямой приказ: call(), apply() и bind() Эти три товарища — твои лучшие друзья, они прямо в прототипе у каждой функции висят.

  • func.call(thisArg, arg1, arg2, ...) Ты прямо командуешь: «Функция, вызывайся вот в этом контексте (thisArg), и вот тебе аргументы по одному». Просто и ясно.

    function greet(greeting, punctuation) {
      console.log(`${greeting}, ${this.name}${punctuation}`);
    }
    
    const person = { name: 'Alice' };
    greet.call(person, 'Hello', '!'); // Вывод: Hello, Alice!

    Всё, this внутри greet теперь — это наш объект person. Никаких неожиданностей.

  • func.apply(thisArg, [argsArray]) Брат-близнец call, но для тех, кто любит работать с массивами. Аргументы ты суёшь не поштучно, а пачкой, в одном массиве.

    const args = ['Hi', '?'];
    greet.apply(person, args); // Вывод: Hi, Alice?

    Охуенно удобно, когда заранее не знаешь, сколько аргументов на тебя свалится.

  • func.bind(thisArg, arg1, arg2, ...) А вот это, чувак, не вызов, а создание новой сущности. bind не вызывает функцию сразу. Он возвращает тебе новую функцию, у которой намертво, намертво приклеен нужный контекст thisArg. Как кандалы.

    const boundGreet = greet.bind(person, 'Hey');
    boundGreet('...'); // Вывод: Hey, Alice...
    // Контекст person теперь зафиксирован навеки. Можно спать спокойно.

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

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

const obj = {
  value: 'My Value',
  regularFunc: function() {
    console.log(this.value); // 'My Value' (this = obj, всё ок)
  },
  arrowFunc: () => {
    console.log(this.value); // undefined (this взялся снаружи, например, из global)
  }
};

const newContext = { value: 'New Value' };
obj.regularFunc.call(newContext); // 'New Value' (контекст поменяли)
obj.arrowFunc.call(newContext);   // undefined (контекст НЕ поменяли, похуй на call)

Запомни: стрелочные — для фиксации контекста родителя, обычные — когда тебе нужен динамический this.

3. Магия слова new Когда вызываешь функцию через new, происходит ёперный театр: создаётся новый пустой объект, и this внутри конструктора начинает указывать именно на него.

function Car(model) {
  this.model = model; // this теперь привязан к новенькому экземпляру
}
const myCar = new Car('Tesla');
console.log(myCar.model); // 'Tesla'

Вот и вся магия. Никаких духов, только строгие правила языка.

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