← Назад к вопросам

Всегда ли у функции один и тот же контекст

2.0 Middle🔥 111 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Контекст функции (this): всегда ли одинаков?

Нет, контекст функции (значение this) не гарантирован и зависит от способа вызова функции. Это одна из самых запутанных частей JavaScript.

Что такое контекст (this)?

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

Способы вызова функции и контекст

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

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

const person = {
  name: 'Иван',
  sayName: function() {
    console.log(this.name); // this = person
  }
};

person.sayName(); // Вывод: Иван

2. Обычный вызов функции

Если функция вызывается как обычная функция (не метод), this указывает на window (в браузере) или global (в Node.js). В strict mode - undefined:

function greet() {
  console.log(this);
}

greet(); // В браузере: window, в strict mode: undefined

const obj = { greet };
obj.greet(); // Вывод: obj (контекст изменился!)

3. Вызов через call() / apply() / bind()

Их явно указывается контекст:

function introduce(age) {
  console.log(${this.name}, мне ${age}`);
}

const alice = { name: 'Алиса' };
const bob = { name: 'Боб' };

introduce.call(alice, 25);     // "Я Алиса, мне 25"
introduce.call(bob, 30);       // "Я Боб, мне 30"
introduce.apply(alice, [25]);  // То же самое, но массив аргументов

const greetAlice = introduce.bind(alice);
greetAlice(25); // "Я Алиса, мне 25" (контекст закреплен)

4. Конструктор (new)

При вызове с new функция работает как конструктор, и this указывает на новый объект:

function User(name) {
  this.name = name;  // this = новый объект
  this.sayName = function() {
    console.log(this.name);
  };
}

const user1 = new User('Мария');  // this = user1
const user2 = new User('Петр');   // this = user2

user1.sayName(); // Мария
user2.sayName(); // Петр

Стрелочные функции (Arrow Functions) - исключение

Стрелочные функции не имеют собственного this. Они наследуют this из внешней области видимости:

const person = {
  name: 'Анна',
  sayName: function() {
    // обычная функция
    console.log(this.name); // this = person
  },
  delay: function() {
    // Проблема: обычная функция
    setTimeout(function() {
      console.log(this.name); // this = window (потеряли контекст!)
    }, 1000);
  },
  delayArrow: function() {
    // Решение: стрелочная функция
    setTimeout(() => {
      console.log(this.name); // this = person (правильно!)
    }, 1000);
  }
};

person.sayName();    // Анна
person.delay();      // undefined (ошибка)
person.delayArrow(); // Анна (правильно)

Проблема 1: Потеря контекста в callback'ах

class Counter {
  count = 0;
  
  increment() {
    this.count++;
  }
  
  // Плохо: потеря контекста
  setupBad() {
    document.addEventListener('click', this.increment);
    // this.increment вызовется с контекстом document, не Counter
  }
  
  // Решение 1: Стрелочная функция
  setupGood1() {
    document.addEventListener('click', () => this.increment());
  }
  
  // Решение 2: bind()
  setupGood2() {
    document.addEventListener('click', this.increment.bind(this));
  }
}

Проблема 2: Контекст в методах класса

class User {
  name = 'John';
  
  // Метод класса
  greet() {
    return `Привет, я ${this.name}`;
  }
}

const user = new User();
console.log(user.greet()); // Привет, я John

// Деструктурируем метод
const { greet } = user;
console.log(greet()); // Ошибка! this = undefined

// Решение: стрелочный метод
class User2 {
  name = 'John';
  
  greet = () => {
    return `Привет, я ${this.name}`; // this всегда = user
  };
}

const user2 = new User2();
const { greet: greet2 } = user2;
console.log(greet2()); // Привет, я John (правильно)

Проблема 3: Контекст в React

class Counter extends React.Component {
  count = 0;
  
  // Плохо: потеря контекста
  incrementBad() {
    this.count++; // this = undefined в callback
  }
  
  // Решение 1: bind в конструкторе
  constructor(props) {
    super(props);
    this.incrementGood1 = this.incrementGood1.bind(this);
  }
  
  incrementGood1() {
    this.count++;
  }
  
  // Решение 2: стрелочный метод
  incrementGood2 = () => {
    this.count++; // this всегда = компонент
  };
  
  render() {
    return (
      <div>
        <button onClick={this.incrementGood1}>Count: {this.count}</button>
        <button onClick={this.incrementGood2}>Count: {this.count}</button>
        {/* Плохо: create new function каждый раз */}
        <button onClick={() => this.count++}>Count: {this.count}</button>
      </div>
    );
  }
}

Таблица: Контекст в разных ситуациях

Способ вызоваЗначение this
obj.method()obj
func()window (браузер) или undefined (strict)
new Func()новый объект
func.call(obj, ...)obj
func.apply(obj, [...])obj
func.bind(obj)obj (закреплено)
Стрелочная функцияthis из внешней области

Практический пример: Сравнение контекстов

const logger = {
  name: 'Logger',
  
  // 1. Обычный вызов
  log: function(msg) {
    console.log(`[${this.name}] ${msg}`);
  },
  
  // 2. Стрелочный метод
  logArrow: (msg) => {
    console.log(`[${this.name}] ${msg}`); // this = window, undefined
  }
};

logger.log('Test');           // [Logger] Test
logger.logArrow('Test');      // [undefined] Test

// Деструктуризация
const { log, logArrow } = logger;
log('Test');                  // Ошибка: this = undefined
logArrow('Test');             // [undefined] Test

Современный подход: функциональные компоненты

В React с функциональными компонентами и хуками эта проблема вообще не существует:

function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = () => setCount(c => c + 1);
  
  return (
    <button onClick={increment}>Count: {count}</button>
  );
  // Никаких проблем с this!
}

Итого

Контекст функции НЕ всегда одинаков:

  1. Зависит от способа вызова (метод, функция, конструктор, call/apply/bind)
  2. Стрелочные функции наследуют контекст из внешней области
  3. Это источник багов - нужно быть внимательным с callback'ами
  4. В современном React (функциональные компоненты) проблема не существует

Правило: Если ты не уверен с контекстом - используй стрелочные функции или bind(). Это избежит большинства проблем с this.

Всегда ли у функции один и тот же контекст | PrepBro