Ключевое слово this
— одна из особенностей JavaScript. Изначально оно пришло из Java, чтобы помочь в реализации объектно-ориентированного программирования. А особенность в том, что это слово может означать разные объекты в зависимости от того, где оно написано. Разбираемся, как это работает, на примерах с кодом.
Что такое this
В JavaScript this
— это ссылка на какой-то объект. Особенность в том, что объект, на который ссылается this
, может меняться в зависимости от контекста вызова. Это как показать пальцем на что-то в коде и сказать «Вот, я имею в виду вот этот объект». Хитрость в том, что «вот это» в коде может быть разным — и всё зависит от того, кто хочет использовать этот объект.
Чаще всего this
определяется тем, кто вызывает функцию. В этом его отличие от области видимости, которая определяется местом вызова функции. Из-за этого в коде часто можно встретить много вызовов this
, только ссылаться они будут на разные объекты.
Теперь посмотрим, как меняется значение this
в разных ситуациях: от простого вызова без контекста до сложных функций.
Простой вызов
В общем случае this
указывает на глобальный объект. Для браузера такой объект — это само окно браузера, поэтому если набрать в консоли браузера
console.log(this);
то мы увидим, что здесь this
привязано к глобальному контексту, а именно к объекту window в браузере.
Когда в JS включён строгий режим, то правила меняются. Если функция вызывается в строгом режиме use strict
, то this
будет равно undefined
. Про строгий режим поговорим в другой раз; если что, по умолчанию он выключен, поэтому this
можно использовать даже в простом вызове.
This ссылается на объект
Когда this
используется внутри объекта, то ссылается на сам объект. Допустим, мы создали объект dog
с тремя методами и используем в одном из его методов this
. Так как this
внутри метода указывает на текущий объект, в контексте которого метод был вызван, то в нашем примере мы получим ссылку на объект dog
.
Напишем простой код с объектом и выведем в консоль значение this
:
var dog = {
name: 'Cheems',
breed: 'Shiba Inu',
intro: function(){
console.log(this);
}
};
dog.intro();
В консоли мы увидим представление объекта со всеми методами:
Использование this
делает код более гибким. Значение this
само привязывается к объекту, в контексте которого он вызван. Это позволяет методу работать с данными конкретного экземпляра объекта и делать код более универсальным.
Режим конструктора
Конструктор — это функция, которую используют, чтобы создавать однотипные объекты. Название конструктора в JavaScript должно быть существительным и начинаться с большой буквы. Конструкторы вызывают с помощью ключевого слова new
. Задача конструктора — объяснить компьютеру, с какими параметрами и свойствами нужно будет создавать новый объект.
Сделаем конструктор и создадим новый объект. Обратите внимание, как мы используем this: оно автоматически указывает на объект в текущем конструкторе, откуда его вызывают:
function Dog () {
// this привязывается к новому объекту
this.name = 'Cheems'
}
// создаём новый объект Dog
const firstDog = new Dog()
firstDog.name === 'Cheems'
console.log(firstDog.name);
Такой подход удобен тем, что с помощью this
и единственной функции-конструктора мы можем создавать множество однотипных объектов и указывать напрямую, к какому объекту обращаемся.
Покажем, как это работает, на примере функции-конструктора для создания объектов автомобилей:
function Car(brand, model, year) {
// используем this, чтобы установить свойства объектов
this.brand = brand;
this.model = model;
this.year = year;
}
// создаём новые объекты с помощью функции-конструктора
const car1 = new Car('Toyota', 'Camry', 2022);
const car2 = new Car('Honda', 'Accord', 2021);
const car3 = new Car('Ford', 'Mustang', 2020);
console.log(car1);
console.log(car2);
console.log(car3);
Если бы мы писали подобную логику без использования this
, то сначала бы создали новый объект, установили его свойства, вернули объект и уже после этого создавали новые. Тогда код получился бы более громоздким.
Работа с методами call() и apply()
В JS объекты чаще всего обладают собственными свойствами и методами, при этом разные объекты не могут просто так использовать методы друг друга. Но иногда нужно обойти это ограничение. Для этого используют методы call()
и apply()
— они позволяют вызывать нужные функции в контексте других объектов. Чтобы передать нужный контекст в функцию, как раз и используют this
— он хранит контекст вызова, который не меняется во время передачи в другую функцию.
Вызов функции через методы call()
или apply()
называют непрямым вызовом.
В синтаксисе методов есть небольшое различие: call()
принимает аргументы списком через запятую, apply()
принимает массив аргументов. В остальном они работают плюс-минус одинаково.
Для наглядности сделаем так:
- Создадим функцию, которая берёт в качестве аргумента приветственное слово и смайлик.
- Внутри этой функции используем this — это поможет нам получить контекст того объекта, который вызвал эту функцию.
- Создадим две переменные с именами.
- Вызовем функцию приветствия, используя переменные с именами как аргументы.
- Так как мы использовали this внутри функции, она получит нужный контекст: перенесёт его из переменных с именами.
- В итоге функция приветствия по контексту поймёт, что нам нужно взять имена из переменных и добавить их в вывод в консоль.
function greet(greetWord, emoticon) {
console.log(`${greetWord} ${this.name} ${emoticon}`)
}
const user1 = { name: 'Barbie' }
const user2 = { name: 'Ken' }
greet.call(user1, 'Hi,', ':-)')
greet.call(user2, 'Hello,', ':-D')
greet.apply(user1, ['Hi,', ':-)'])
greet.apply(user2, ['Hello,', ':-D'])
Стрелочные функции
Стрелочная функция не создаёт свой контекст исполнения, а берёт его из своей внешней функции, в которой эта стрелочная функция определена.
Создадим функцию, которая выводит приветствие и имя пользователя, а затем спустя некоторое время повторяет приветствие:
function greetWaitAndAgain() {
console.log(`Hi, ${this.name}!`)
setTimeout(() => {
console.log(`Hi again, ${this.name}!`)
})
}
// создаём объект
const user = { name: 'Barbie' }
// добавляем метод к объекту
user.greetWaitAndAgain = greetWaitAndAgain;
user.greetWaitAndAgain()
Если бы мы использовали обычную функцию, то контекст бы потерялся и this
уже не указывало на объект user
, и тогда пришлось бы использовать непрямой вывоз. По этой причине this
часто используют в стрелочных функциях, чтобы не перегружать код дополнительными методами.
Как понять, чему равно значение this
Определяем, чему равно this
в каждом конкретном случае:
- Если мы не находимся внутри функции, то
this
равно глобальному объекту — окну браузера. - Если функция получена как свойство объекта и сразу же используется, то
this
будет равно этому объекту. - Если функция вызывается с помощью оператора
new
, тоthis
будет ссылаться на вновь созданный объект в конструкторе функции - Если функция создана с помощью метода
call
илиapply
, то значениеthis
будет аргументом этой функции - Если мы внутри стрелочной функции, то
this
равно значениюthis
, находящемуся вне этой функции.
Зачем такие сложности?
Ключевое слово this
хорошо раскрывает динамическую природу JavaScript, даёт большую гибкость и сокращает дублирование кода. Например, с помощью this
можно писать очень абстрактные функции, которые будут использовать контекст выполнения для своей работы. Так мы можем добиться полиморфизма, а оттуда уже до ООП недалеко.