Kodokon kodokon.com

this, прототипы и классы

Пойми, как this определяется динамически, как работает цепочка прототипов и что на самом деле классы добавляют к объектной модели.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

Значение this решается в месте вызова, а не при объявлении. Четыре правила по убыванию приоритета: вызов через new, явная привязка (call, apply, bind), вызов как метода (obj.fn()) и обычный вызов (fn()), который в строгом режиме даёт undefined. Отсюда напрямую следует самый частый баг из практики: если вытащить метод из его объекта, неявная привязка ломается.

JAVASCRIPT
const user = {
  name: "Ada",
  greet() {
    return "Hello, " + this.name;
  },
};
console.log(user.greet()); // "Hello, Ada"
const detached = user.greet;
// detached(); // TypeError: this is undefined
const bound = user.greet.bind(user);
console.log(bound()); // "Hello, Ada"
Неявная привязка исчезает, как только метод извлечён.

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

JAVASCRIPT
function Animal(name) {
  this.name = name;
}
Animal.prototype.describe = function () {
  return this.name + " is an animal";
};
const dog = new Animal("Rex");
console.log(dog.describe()); // "Rex is an animal"
console.log(Object.getPrototypeOf(dog) ===
  Animal.prototype); // true
console.log(dog.hasOwnProperty("describe")); // false
describe живёт на прототипе и общий для всех экземпляров.

Синтаксис class не меняет эту модель: он делает её читаемой. Методы, объявленные в теле класса, попадают на Constructor.prototype и потому разделяются. Поля экземпляра (включая стрелочные функции, присвоенные как поля) пересоздаются для каждого экземпляра: привязка this гарантирована, но расход памяти умножается. Наконец, поля #private дают приватность, которую обеспечивает сам движок, в отличие от договорённости с _подчёркиванием.

JAVASCRIPT
class BankAccount {
  #balance = 0;
  deposit(amount) {
    if (amount <= 0) throw new Error("Invalid amount");
    this.#balance += amount;
    return this;
  }
  get balance() {
    return this.#balance;
  }
}
const account = new BankAccount();
account.deposit(50).deposit(25);
console.log(account.balance); // 75
Приватное поле под контролем движка и цепочечный API.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Чему равен this в строгом режиме при обычном вызове fn() без объекта и без явной привязки?
    • globalThis
    • undefined
    • Объекту, в котором была объявлена fn
    • null
  2. Где хранятся методы, объявленные в теле класса (не считая полей)?
    • На каждом экземпляре, копируются при конструировании
    • На Constructor.prototype, общие для всех экземпляров
    • В недоступной внутренней области видимости
  3. Что особенного у стрелочной функции в отношении this?
    • Она привязывает this к вызывающему объекту, как метод
    • У неё нет своего this, и она захватывает его из охватывающей области
    • Она всегда привязывает this к globalThis
    • Она запрещает любой доступ к this