this в JavaScript: простое руководство с примерами, ошибками и лучшими практиками

this в JavaScript: простое руководство с примерами, ошибками и лучшими практиками

this в JavaScript: простое руководство с примерами, ошибками и лучшими практиками

Запрос «this в JavaScript» остаётся одним из самых популярных среди начинающих и продолжающих разработчиков. Этот ключевой концепт влияет на поведение методов, классов, обработчиков событий и коллбэков. Разберёмся детально и практично, с примерами и анти-примерами.

Что такое this в JavaScript

Ключевое слово this — это ссылка на текущий контекст выполнения. Важно: в JavaScript значение this определяется способом вызова функции, а не местом её объявления (кроме стрелочных функций, о них ниже).

function show() {
  console.log(this);
}
show(); // Значение зависит от режима: window (нестрогий) или undefined (strict)

Глобальный контекст и строгий режим

  • В браузере без strict-режима: this === window.
  • В strict-режиме: this === undefined.
  • В модулях ES (type=»module»): на верхнем уровне this === undefined.
  • 'use strict';
    function foo() {
      console.log(this); // undefined
    }
    foo();
    

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

    Когда функция вызвана как метод объекта, this указывает на этот объект.

    const user = {
      name: 'Алиса',
      say() {
        console.log('Привет, ' + this.name);
      }
    };
    user.say(); // Привет, Алиса
    
    const speak = user.say;
    speak(); // В strict: ошибка/undefined, без strict: window — this потерян
    

    Вывод: при передаче метода как коллбэка this теряется. Решения — bind, стрелочная обёртка или явный вызов через call/apply.

    Конструкторы и классы: new

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

    function Person(name) {
      this.name = name;
    }
    const p = new Person('Боб');
    console.log(p.name); // Боб
    
    class Counter {
      constructor() {
        this.value = 0;
      }
      inc() { this.value++; }
    }
    const c = new Counter();
    c.inc();
    console.log(c.value); // 1
    

    call, apply и bind: ручное управление this

    Методы call и apply вызывают функцию немедленно с заданным this. Разница — в передаче аргументов.

    function greet(greeting, punctuation) {
      console.log(greeting + ', ' + this.name + punctuation);
    }
    const ctx = { name: 'Кира' };
    
    greet.call(ctx, 'Привет', '!');   // Привет, Кира!
    greet.apply(ctx, ['Здравствуйте', '!!']); // Здравствуйте, Кира!!
    

    bind возвращает новую функцию с «прикреплённым» this (и, опционально, частично применёнными аргументами).

    const user = { name: 'Лев' };
    function sayHi() { console.log('Hi, ' + this.name); }
    
    const sayHiFromUser = sayHi.bind(user);
    sayHiFromUser(); // Hi, Лев
    

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

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

    const counter = {
      value: 0,
      incLater() {
        setTimeout(() => {
          this.value++;
          console.log(this.value);
        }, 100);
      }
    };
    counter.incLater(); // 1 — стрелка взяла this из incLater()
    

    Анти-пример: стрелка как метод объекта — часто ошибка.

    const obj = {
      name: 'X',
      say: () => console.log(this.name)
    };
    obj.say(); // this не obj; в модуле: undefined, в браузере вне strict: window
    

    Обработчики событий в DOM

    В addEventListener при обычной функции this указывает на элемент, на котором висит обработчик. В стрелке — на внешний контекст. Надёжнее использовать event.currentTarget.

    document.querySelector('#btn').addEventListener('click', function(e) {
      console.log(this === e.currentTarget); // true
    });
    
    document.querySelector('#btn2').addEventListener('click', (e) => {
      console.log(this); // лексический this, не элемент
      console.log(e.currentTarget); // правильный способ доступа к элементу
    });
    

    setTimeout/setInterval: потеря контекста

    Если передать метод напрямую в таймер, this, скорее всего, потеряется.

    const timerDemo = {
      v: 0,
      inc() { this.v++; },
      run() {
        setTimeout(this.inc, 50); // потеряем this
        setTimeout(() => this.inc(), 100); // ок
        setTimeout(this.inc.bind(this), 150); // ок
      }
    };
    
    timerDemo.run();
    

    this в массивах и переборах

    У многих методов массивов есть необязательный второй аргумент thisArg.

    const tools = {
      prefix: '#',
      addPrefix(x) { return this.prefix + x; }
    };
    const res = ['a','b'].map(tools.addPrefix, tools);
    console.log(res); // ['#a','#b']
    

    Без передачи thisArg будет ошибка/undefined внутри addPrefix.

    Частые ошибки и как их избегать

  • Деструктуризация методов: const { push } = Array.prototype; push(…)
  • Передача методов как коллбэков без bind.
  • Использование стрелок как методов объекта (теряется ожидаемое this).
  • Ожидание window в модулях: на верхнем уровне this === undefined.
  • // Исправление деструктуризации
    const push = Array.prototype.push.call.bind(Array.prototype.push);
    const arr = [1];
    push(arr, 2);
    console.log(arr); // [1,2]
    

    Памятка по this в JavaScript

  • Обычный вызов функции: this — undefined (strict) или window (без strict).
  • Метод объекта: this — сам объект.
  • Конструктор/new: this — новый экземпляр.
  • call/apply/bind: this задаётся явно.
  • Стрелка: this берётся снаружи, своего нет.
  • DOM-обработчики: this — элемент (для обычной функции), используйте event.currentTarget.
  • Мини-практика: безопасный счётчик

    class SafeCounter {
      value = 0;
      // полевой синтаксис со стрелкой фиксирует this без bind в конструкторе
      inc = () => { this.value++; };
      log = () => console.log(this.value);
    }
    const sc = new SafeCounter();
    const { inc, log } = sc;
    inc(); inc();
    log(); // 2
    

    В классах вы можете выбирать между методами прототипа и стрелочными полями. Второй вариант часто проще для коллбэков, так как не требует manual bind.

    Советы из практики

  • Для коллбэков и таймеров используйте стрелочные функции или заранее привязывайте методы через bind.
  • В React/Vue/современных фреймворках предпочитайте стрелки для обработчиков или class fields, чтобы не терять this.
  • В модулях не полагайтесь на глобальный this — используйте явные ссылки (window, globalThis).
  • В обработчиках событий чаще используйте event.currentTarget вместо this для читаемости и надёжности.
  • Куда двигаться дальше

    Если хотите системно закрыть пробелы в основах и уверенно писать современный код, рекомендую пройти практический курс: Прокачать JavaScript и отработать this на практике. Много задач, разбор типичных ошибок и пошаговый рост от базовых тем к продвинутым.

    Итоги

    this в JavaScript — динамическая, но логичная концепция. Поймите, как функция вызвана, и вы почти всегда предскажете значение this. Используйте стрелочные функции для коллбэков, bind для методов, а в DOM опирайтесь на event.currentTarget. Эти простые правила избавят от большей части ошибок и сделают код предсказуемым.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий