Ключевое слово this в JavaScript

В этом руководстве вы узнаете о значении 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.

Рейтинг
( Пока оценок нет )
Александр Русаков / автор статьи
Программист, разработчик, 12 лет опыта работы в крупных компаниях. Быстро освоил typescript, делюсь своими знаниями на страницах этого сайта.
Загрузка ...
JavaScript и TypeScript