В этом руководстве вы узнаете о значении this
в JavaScript и ясно поймете его в различных контекстах.
Если вы работали с другими языками программирования, такими как Java, C# или PHP, вы уже знакомы с ключевым словом this
. В этих языках ключевое слово представляет текущий экземпляр класса. И это актуально только внутри класса.
JavaScript также имеет ключевое слово
this
. Однако это ключевое слово в JavaScript ведет себя иначе, чем в других языках программирования.
В JavaScript вы можете использовать его в глобальном контексте и контексте функции. Более того, поведение ключевого слова меняется между строгим и нестрогим режимами.
Что за ключевое слово this?
В общем, this
ссылается на объект, свойством которого является функция. Другими словами, this
ссылается на объект, который в данный момент вызывает функцию.
Предположим, у вас есть объект с именем counter
, у которого есть метод next()
. Когда вы вызываете метод next()
, вы можете получить доступ к объекту.
let counter = { count: 0, next: function() { return++this.count; }, }; counter.next();
Внутри функции next()
this
ссылается на объект counter
. Следующий вызов метода:
counter.next();
next()
— это функция, являющаяся свойством объекта- counter
. Поэтому внутри функции next()
this
ссылается на объект counter
.
Глобальный контекст
В глобальном контексте this
ссылается на глобальный объект, который является объектом window
в веб-браузере или global
объектом в Node.js.
Это поведение одинаково как в строгом, так и в нестрогом режимах. Вот вывод в веб-браузере:
console.log(this === window); // true
Если вы присвоите свойство this
объекту в глобальном контексте, JavaScript добавит свойство к глобальному объекту, как показано в следующем примере:
this.color= 'Red'; console.log(window.color); // 'Red'
Контекст функции
В JavaScript вы можете вызвать функцию следующими способами:
- Вызов функции
- Вызов метода
- Вызов конструктора
- Косвенный вызов
Каждый вызов функции определяет свой собственный контекст. Поэтому this
ведет себя по-разному.
1) Простой вызов функции
В нестрогом режиме this
ссылается на глобальный объект, тогда функция вызывается следующим образом:
function show() { console.log(this === window); // true } show();
Когда вы вызываете функцию show()
, this
ссылается на глобальный объект, который является window
в веб-браузере и global
в Node.js.
Вызов функции show()
аналогичен:
window.show();
В строгом режиме JavaScript устанавливает this
внутри функции в значение undefined
. Например:
"use strict"; function show() { console.log(this === undefined); } show();
Чтобы включить строгий режим, вы используете директиву "use strict"
в начале файла JavaScript. Если вы хотите применить строгий режим только к определенной функции, поместите его вверху тела функции.
Обратите внимание, что строгий режим доступен начиная с ECMAScript 5.1. strict
режим применяется как к функциям, так и к вложенным функциям. Например:
function show() { "use strict"; console.log(this === undefined); // true function display() { console.log(this === undefined); // true } display(); } show();
Выход:
true true
Во внутренней функции display()
для this
также установлено значение undefined
, как показано в консоли.
2) Вызов метода
Когда вы вызываете метод объекта, JavaScript устанавливает this
на объект, которому принадлежит метод. См. следующий car
объект:
let car = { brand: 'Honda', getBrand: function() { return this.brand; } } console.log(car.getBrand()); // Honda
В этом примере объект this
в getBrand()
ссылается на объект car
.
Поскольку метод является свойством объекта, который является значением, вы можете сохранить его в переменной.
let brand = car.getBrand;
А затем вызвать метод через переменную
console.log(brand()); // undefined
Вы получаете undefined
вместо "Honda"
, потому что, когда вы вызываете метод без указания его объекта, JavaScript устанавливает this
как глобальный объект в нестрогом режиме и undefined
в строгом режиме.
Чтобы решить эту проблему, вы используете метод bind()
объекта Function.prototype
. Метод bind()
создает новую функцию, ключевое слово this
которой имеет указанное значение.
let brand = car.getBrand.bind(car); console.log(brand()); // Honda
В этом примере при вызове метода brand()
ключевое слово this
привязывается к объекту car
. Например:
let car = { brand: 'Honda', getBrand: function() { return this.brand; } } let bike = { brand: 'Harley Davidson' } let brand = car.getBrand.bind(bike); console.log(brand());
Выход:
Harley Davidson
В этом примере метод bind()
устанавливает this
для объекта bike
, поэтому вы видите значение свойства brand
объекта bike
на консоли.
3) Вызов конструктора
Когда вы используете ключевое слово new
для создания экземпляра объекта функции, вы используете функцию как конструктор.
В следующем примере функция Car
объявляется, а затем вызывается как конструктор:
function Car(brand) { this.brand = brand; } Car.prototype.getBrand = function() { return this.brand; } let car = new Car('Honda'); console.log(car.getBrand());
Выражение new Car('Honda')
является вызовом конструктора функции Car
.
JavaScript создает новый объект и присваивает this
только что созданный объект. Этот шаблон отлично работает только с одной потенциальной проблемой.
Теперь вы можете вызывать Car()
как функцию или как конструктор. Если вы опустите ключевое слово new
следующим образом:
var bmw = Car('BMW'); console.log(bmw.brand); // => TypeError: Cannot read property 'brand' of undefined
Поскольку значение this
в Car()
устанавливается на глобальный объект, bmw.brand
возвращает значение undefined
.
Чтобы убедиться, что функция Car()
всегда вызывается с помощью вызова конструктора, вы добавляете проверку в начале функции Car()
следующим образом:
function Car(brand) { if (! (this instanceof Car)) { throw Error('Must use the new operator to call the function'); } this.brand = brand; }
В ES6 появилось мета-свойство с именем new.target
, которое позволяет определить, вызывается ли функция как простой вызов или как конструктор.
Вы можете изменить функцию Car()
, использующую new.target
, следующим образом:
function Car(brand) { if (!new.target) { throw Error('Must use the new operator to call the function'); } this.brand = brand; }
4) Косвенный
В JavaScript функции являются гражданами первого класса. Другими словами, функции — это объекты, являющиеся экземплярами типа Function.
Тип Function
имеет два метода: call()
и apply()
. Эти методы позволяют вам установить значение this
при вызове функции. Например:
function getBrand(prefix) { console.log(prefix + this.brand); } let honda = { brand: 'Honda' }; let audi = { brand: 'Audi' }; getBrand.call(honda, "It's a "); getBrand.call(audi, "It's an ");
Выход:
It's a Honda It's an Audi
В этом примере мы вызвали getBrand()
косвенно, используя метод call()
функции getBrand
. Мы передали объект honda
и audi
в качестве первого аргумента метода call()
, поэтому при каждом вызове мы получали соответствующую марку.
Метод apply()
подобен методу call()
, за исключением того, что его второй аргумент представляет собой массив аргументов.
getBrand.apply(honda, ["It's a "]); // "It's a Honda" getBrand.apply(audi, ["It's an "]); // "It's a Audi"
Стрелочные функции
ES6 представил новую концепцию, названную стрелочной функцией. В стрелочных функциях JavaScript устанавливает this
лексически.
Это означает, что стрелочная функция не создает свой собственный контекст выполнения, а наследует this
от внешней функции, в которой определена стрелочная функция. См. следующий пример:
let getThis =() => this; console.log(getThis() === window); // true
В этом примере значение this
установлено на глобальный объект, т. е. window
в веб-браузере.
Поскольку стрелочная функция не создает собственного контекста выполнения, определение метода с использованием стрелочной функции вызовет проблему. Например:
function Car() { this.speed = 120; } Car.prototype.getSpeed = () = > { return this.speed; } var car = new Car(); car.getSpeed(); // TypeError
Внутри getSpeed()
значение this
ссылается на глобальный объект, а не на объект Car
. Поэтому car.getSpeed()
вызывает ошибку, поскольку глобальный объект не имеет свойства speed
.