Хватит «плавать» в базовых концепциях. Давайте разберемся с ключевым словом 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
.
3 комментария
Вы выполнили
new Game()
- создался первый экземпляр классаGame
. Вы вызываете у него методstart
. Внутри этого метода this ссылается на сам этот первый экземпляр.Внутри start вы еще раз вызываете
new Game
, это создает другой - второй экземпляр классаGame
. Разные экземпляры не равны друг другу.Чтобы равенство было верным, вместо new Game можно поставить только
this