this

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

Зачем нужно

Понимание this — обязательное условие для работы с объектами, классами, DOM-обработчиками и фреймворками. Неправильное понимание this — источник большинства багов у начинающих разработчиков.

Где используется

Методы объектов, классы, конструкторы, обработчики событий, callback-функции, React-компоненты, jQuery, Node.js.

Предпосылки

Function Declaration, Arrow Function, Замыкания (Closures)

Четыре правила привязки this

1. Default binding — вызов без контекста

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

showThis;
// В strict mode: undefined
// Без strict mode: window (globalThis)

'use strict';
function strictFn() {
  console.log(this); // undefined
}
strictFn;

2. Implicit binding — вызов как метод объекта

const user = {
  name: 'Иван',
  greet {
    console.log(`Привет, ${this.name}`);
  }
};

user.greet; // "Привет, Иван" — this === user

3. Explicit binding — call, apply, bind

function greet() {
  console.log(`Привет, ${this.name}`);
}

const user = { name: 'Мария' };

greet.call(user);  // "Привет, Мария"
greet.apply(user); // "Привет, Мария"

const bound = greet.bind(user);
bound;           // "Привет, Мария"

4. New binding — вызов с new

function User(name) {
  // this = {} (новый пустой объект)
  this.name = name;
  this.greet = function {
    console.log(`Привет, ${this.name}`);
  };
  // return this; (неявно)
}

const ivan = new User('Иван');
ivan.greet; // "Привет, Иван" — this === ivan

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

От наивысшего к низшему:

1. new binding          — new Fn
2. Explicit binding     — call/apply/bind
3. Implicit binding     — obj.method
4. Default binding      — fn
function greet() {
  console.log(this.name);
}

const obj1 = { name: 'Один', greet };
const obj2 = { name: 'Два' };

// Implicit
obj1.greet; // "Один"

// Explicit > Implicit
obj1.greet.call(obj2); // "Два"

// new > Explicit
const BoundGreet = greet.bind(obj2);
const instance = new BoundGreet();
// this — новый объект, НЕ obj2

this в разных контекстах

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

console.log(this); // В браузере: window, в Node.js модуле: module.exports

// В ES-модуле
console.log(this); // undefined

Метод объекта

const calculator = {
  value: 0,
  add(n) {
    this.value += n;
    return this; // для цепочки вызовов
  },
  subtract(n) {
    this.value -= n;
    return this;
  },
  getResult {
    return this.value;
  }
};

const result = calculator.add(10).add(5).subtract(3).getResult;
console.log(result); // 12

Конструктор и класс

class Animal {
  constructor(name) {
    this.name = name; // this — создаваемый экземпляр
  }

  speak {
    console.log(`${this.name} говорит`);
  }
}

class Dog extends Animal {
  speak {
    console.log(`${this.name} лает`); // this — экземпляр Dog
  }
}

const dog = new Dog('Бобик');
dog.speak; // "Бобик лает"

Обработчик события

const button = document.querySelector('#myBtn');

button.addEventListener('click', function(event) {
  console.log(this);           // <button> элемент
  console.log(event.target);   // тот же <button>
  this.classList.toggle('active');
});

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

const team = {
  name: 'Альфа',
  members: ['Иван', 'Мария'],

  showMembers {
    // Стрелочная функция берёт this из showMembers
    this.members.forEach(member => {
      console.log(`${this.name}: ${member}`);
    });
  }
};

team.showMembers;
// "Альфа: Иван"
// "Альфа: Мария"

Потеря контекста

Передача метода как callback

const user = {
  name: 'Иван',
  greet {
    console.log(`Привет, ${this.name}`);
  }
};

// this потерян!
const fn = user.greet;
fn; // "Привет, undefined"

// setTimeout тоже теряет this
setTimeout(user.greet, 100); // "Привет, undefined"

// Решение 1: bind
setTimeout(user.greet.bind(user), 100);

// Решение 2: обёртка стрелочной функцией
setTimeout( => user.greet, 100);

Деструктуризация метода

const { greet } = user;
greet; // this потерян!

// Решение: не деструктуризируйте методы, или bind

Вложенные функции

const obj = {
  value: 42,
  method {
    // Вложенная функция — свой this
    function inner() {
      console.log(this.value); // undefined (strict) или window.value
    }
    inner;

    // Решение 1: стрелочная функция
    const innerArrow = () => {
      console.log(this.value); // 42
    };
    innerArrow;

    // Решение 2: сохранить this
    const self = this;
    function innerSelf() {
      console.log(self.value); // 42
    }
    innerSelf;
  }
};

this в классах

class Button {
  constructor(label) {
    this.label = label;
    this.clicks = 0;

    // Привязка в конструкторе
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick {
    this.clicks++;
    console.log(`${this.label}: ${this.clicks} кликов`);
  }

  // Альтернатива: class field + arrow
  handleClickArrow = () => {
    this.clicks++;
    console.log(`${this.label}: ${this.clicks} кликов`);
  };
}

const btn = new Button('Кнопка');
document.querySelector('#btn').addEventListener('click', btn.handleClick);

globalThis

// Универсальный глобальный объект (ES2020)
console.log(globalThis);
// В браузере: window
// В Node.js: global
// В Web Worker: self

Частые ошибки

1. this в setTimeout

class Timer {
  constructor {
    this.seconds = 0;
  }
  start {
    // Плохо
    // setInterval(function { this.seconds++; }, 1000);

    // Хорошо
    setInterval(() => { this.seconds++; }, 1000);
  }
}

2. this в обработчике событий React

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    // Без bind: this будет undefined в handleClick
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick {
    this.setState({ count: this.state.count + 1 });
  }
}

3. Ожидание лексического this от обычной функции

const obj = {
  items: [1, 2, 3],
  process {
    // forEach с обычной функцией — this потерян
    this.items.forEach(function(item) {
      // console.log(this.items); // undefined!
    });

    // Решение: стрелка
    this.items.forEach((item) => {
      console.log(this.items); // [1, 2, 3]
    });
  }
};

Практика

  1. Создай объект с методом и вызови его напрямую и через переменную — сравни this
  2. Напиши класс с методом, который теряет this в setTimeout — исправь тремя способами
  3. Используй call/apply для вызова функции с разными контекстами
  4. Создай цепочку вызовов (method chaining) через return this
  5. Определи, что выведет this в 5 разных контекстах (глобальный, метод, конструктор, стрелка, событие)

Связанные темы

Ресурсы


🎓 Источник: Функции, стрелочные функции, контексты, замыкания

  • 📅 2018-09-27 · YouTube · ID: pn5myCmpV2U
  • Тезисы:
    • У функции два контекста: функциональный (аргументы, локальные) и объектный (через this)
    • Не у всех функций есть объектный контекст
    • У lambda (arrow) нет своего this — она берёт его из контекста выше
    • call — аргументы через запятую, apply — массив, первый аргумент — this
  • Цитата: «У функции есть один контекст. Он называется функциональный контекст. Но у нее есть еще объектный контекст. Но не у всех функций есть объектный контекст.»

⚡ Источник: Как работает this в javascript · AsForJS

  • 📅 2023-05-07 · YouTube · ID: 4tg4qokVS9o
  • Тезисы:
    • Главный тезис: this в JS НЕ контекст, никогда им не был
    • this — особый идентификатор, разрешаемый формой вызова
    • Ровно 3 способа задать this: call/apply/bind, new, dot notation
    • По умолчанию this = undefined в strict mode
    • У любой нормальной функции this — скрытый нулевой параметр
    • Стрелочная функция не задаёт this, ищет его как обычный идентификатор по цепочке окружений
    • В non-strict примитив this проходит ToObject (12 → Number{12})
    • API хост-среды (HTML5 addEventListener, Node setTimeout) вправе нарушать правила JS
    • bind перекрывает даже поведение API
  • Цитата: «this в JavaScript — это не контекст, никогда им не было, никогда не будет, ничего общего с контекстом.»
  • Цитата (мнемоника): «Хочешь знать, чему this сравняется, проверь, как твоя функция запускается.»
  • Подробнее: this, this, this в callback и API