Хватит «плавать» в базовых концепциях. Давайте разберемся с ключевым словом this в JavaScript раз и навсегда.

Что такое this?

this указывает на объект, который выполняет текущий кусок JavaScript-кода.

Другими словами, this – это ссылка на текущий контекст выполнения. У каждой функции есть этот контекст. Он указывает, где и как эта функция вызывается.

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

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

Если интересно, загляните сюда:

Пример:

function bike() {
  console.log(this.name);
}

var name = "Ninja";
var obj1 = { name: "Pulsar", bike: bike };
var obj2 = { name: "Gixxer", bike: bike };

bike();           // "Ninja" / в строгом режиме ошибка
obj1.bike();      // "Pulsar"
obj2.bike();      // "Gixxer"

Функция bike обращается к объекту this, пытаясь получить его свойство name и вывести его в консоль. Другими словами, она пытается получить свойство name текущего контекста выполнения.

Если вызвать функцию просто как функцию (bike()), ее контекстом выполнения будет глобальный контекст (window в браузере). Тогда обращение this.name будет равносильно window.name, что в данном коде равно Ninja, так как переменные объявленные через var записываются в свойства глобального объекта.

Внимание! Внимание!

В строгом режиме (use strict — который всегда нужно использовать!) функции вызываемые обычным способом не имеют контекста выполнения (undefined). Поэтому в коде будет ошибка, так как функция bike не может получить поле name объекта, которого не существует.

Если же вызвать функцию bike как метод объекта obj1 (через точку или квадратные скобки — obj1.bike()), то контекстом выполнения этой функции станет сам объект obj1. Аналогично при вызове obj2.bike().

В этом случае this ссылается на объекты obj1 и obj2 соответственно, и поле name запрашивает у них.

Вот и все, что нужно знать о контексте выполнения. Ничего сложного.

Неявная привязка this при вызове метода

Контекст выполнения функции (this) всегда определяется только при ее вызове. Даже если вы объявили некоторую функцию как метод объекта obj1, но вызвали ее в контексте другого объекта obj2, то this будет ссылаться на второй объект, а не на первый. Это неявная привязка контекста — интерпретатор сам определяет, к чему нужно привязаться.

let obj1 = {
  name: "Pulsar",
  bike: function() {
    console.log(this.name);
  }
}
let obj2 = { name: "Gixxer", bike: obj1.bike };

obj1.bike();      // "Pulsar"
obj2.bike();      // "Gixxer"

Повторим для закрепления: не имеет значения, где и как объявлена функция, важно, где и как она вызывается.

Явная привязка контекста

Вы можете явно заменить контекст выполнения функции с помощью методов call и apply. Это методы функций, то есть синтаксис их вызова выглядит так:

function bike() {
  console.log(this.name);
}

let obj = { name: "Pulsar" }

bike.call(obj);   // "Pulsar"

Здесь мы берем функцию bike и сразу же вызываем с жестко указанным контекстом (obj).

Метод apply работает точно так же. Вся разница между ними заключается в способе передачи аргументов в функцию, если они нужны. (Подробнее в документации: call, apply).

Фиксированная привязка контекста

call и apply вызывают функцию с новым контекстом сразу же — на месте. Но иногда требуется просто привязать нужный контекст, а саму функцию вызвать позже или даже передать ее в другое место программы. Для этого существует метод bind.

let bike = function() {
  console.log(this.name);
}

let obj1 = { name: "Pulsar" };
let obj2 = { name: "Gixxer" };

let bindBike = bike.bind(obj1);

bindBike();           // "Pulsar"
bindBike.call(obj2);  // "Pulsar"

К функции bindBike навсегда привязан контекст obj1. Это значит, что при вызове не будет происходить неявная привязка. Не имеет значения, вызовете ли вы эту функцию напрямую или как метод другого объекта, у нее всегда будет один и тот же this.

Ключевое слово new

new в JavaScript тесно связано с this, поэтому следует о нем упомянуть. Если перед вызванной функцией поставить new, она сразу же превратится в конструктор. Это значит, что при ее выполнении:

  • будет создан новый пустой объект;
  • этот объект будет установлен как this для этой функции;
  • если функция ничего не возвращает явно, будет возвращен этот объект.
function bike() {
  console.log(this);
  this.maker = "Kawasaki"; 
}

bike(); // ошибка в строгом режиме
let obj = new bike(); // {}
console.log(obj.maker);  // "Kawasaki"

Первый вызов (обычная функция) выведен в консоль undefined и выбросит ошибку, так как нельзя установить свойство maker для несуществующего объекта.

Второй вызов (конструктор) выведен в консоль пустой объект, который функция bike сможет заполнить новыми свойствами.

Приоритет способов привязки this

Когда интерпретатор пытается найти подходящий контекст выполнения для вызываемой функции, он проделывает следующую последовательность действий:

  • Сначала проверяет, не вызвана ли функция как конструктор с ключевым словом new. В этом случае контекстом всегда будет пустой объект;
  • Затем смотрит на фиксированные привязки (bind);
  • Потом на явные привязки call или apply;
  • После этого пытается неявно привязать к объекту, если функция вызвана как метод (через точку или квадратные скобки);
  • Если и это не удается (обычный вызов функции), то в качестве контекста выполнения устанавливается глобальный объект (window) или undefined.

Статьи про JavaScript

3 комментария

  • andrey
    andrey
    12/03/2024, 00:43
    class Game { constructor() { console.log(this) this.board = 12; } start() { console.log(this === (new Game)) console.log(this) console.log(new Game) console.log((new Game).board + 10) } } new Game().start() Добрый день ,почему у меня не может стать приравнятся this и стать true вот здесь (this === (new Game)) и что надо поставить вместо new Game чтоб стало true
    • Furry_web
      Furry_web
      12/03/2024, 21:59
      Внутри метода start, ключевое слово this ссылается на конкретный экземпляр класса Game.

      Вы выполнили new Game() - создался первый экземпляр класса Game. Вы вызываете у него метод start. Внутри этого метода this ссылается на сам этот первый экземпляр.

      Внутри start вы еще раз вызываете new Game, это создает другой - второй экземпляр класса Game. Разные экземпляры не равны друг другу.

      Чтобы равенство было верным, вместо new Game можно поставить только this

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

*Доступные HTML-теги: a, abbr, blockquote, code, pre, del, i, em, strong, b, strike
*Не будет опубликован