Ключевое слово this
Поведение ключевого слова this
зависит от контекста, в котором оно используется, и от того, в каком режиме оно используется - строгом или нестрогом.
Глобальный контекст и объект globalThis
В глобальном контексте this
ссылается на глобальный объект. Что такое "глобальный объект" в JavaScript? Это зависит от среды, в которой выполняется код. Так, в веб-браузере this
представляет объект window
- объект, который представляет окно браузера. В среде Node.js this
представляет объект global
. А для веб-воркеров this
представляет объект self
.
Например, в веб-браузере при выполнении следующего кода:
console.log(this);
мы получим консольный вывод вроде следующего:
Window {window: Window, self: Window, document: document, name: "", location: Location, …}
В стандарт ES2020 было добавлено определение объекта globalThis
, который позволяет ссылаться на глобальный конекст вне зависимости, в какой среде и в какой ситуации выполняется код:
console.log(globalThis);
Контекст функции
В пределах функции this
ссылается на внешний контекст. Для функций, определенных в глобальном контексте, это объект globalThis
:
function foo(){
var bar = "bar2";
console.log(this.bar);
}
var bar = "bar1";
// bar1
foo();
Если бы мы не использовали this
, то обращение шло бы к локальной переменной, определенной внутри функции:
function foo(){
var bar = "bar2";
console.log(bar);
}
var bar = "bar1";
// bar2
foo();
Но если бы мы использовали строгий режим strict mode
, то this
в этом случае имело бы значение undefined
:
"use strict";
function foo(){
var bar = "bar2";
console.log(this.bar);
}
var bar = "bar1";
// ошибка - this - undefined
foo();
Контекст объекта
В контексте объекта, в том числе в его методах, ключевое слово this
ссылается на этот же объект:
var o = {
bar: "bar3",
foo: function(){
console.log(this.bar);
}
}
var bar = "bar1";
// bar3
o.foo();
Примеры this
Рассмотрим более сложный пример:
function foo(){
var bar = "bar2";
console.log(this.bar);
}
var o3 = {bar:"bar3", foo: foo};
var o4 = {bar:"bar4", foo: foo};
var bar = "bar1";
// bar1
foo();
// bar3
o3.foo();
// bar4
o4.foo();
Здесь определена глобальная переменная bar
. И также в функции foo
определена локальная переменная bar
. Значение какой переменной будет выводиться в функции foo
? Функция foo
выводит значение глобальной переменной, так как данный скрипт запускается в нестрогом режиме, а значит ключеое слово this
в функции foo
ссылается на внешний контекст.
Иначе дело обстоит с объектами. Они определяют свой собственный контекст, в котором существует свое свойство bar
. И при вызове метода foo
внешним контекстом по отношению к функции будет контекст объектов o3
и o4
.
Подобное поведение может привести к некоторому непонимаю в отдельных случаях. Так, рассмотрим другую ситуацию:
var o1 = {
bar: "bar1",
foo: function(){
console.log(this.bar);
}
}
var o2 = {bar: "bar2", foo: o1.foo};
var bar = "bar3";
var foo = o1.foo;
// bar1
o1.foo();
// bar2
o2.foo();
// bar3
foo();
Несмотря на то, что объект o2
использует метод foo
из объекта o1
, тем не менее функция o1.foo
также будет искать значение для this.bar
во внешнем котексте, то есть в контексте объекта o2
. А в объекте o2
это значение равно bar
: bar2
.
То же самое с глобальной переменной foo
, которая ссылается на ту же функцию, что и метод o1.foo
. В этом случае также будет происходить поиск значения для this.bar
во внешним контексте, то есть в глобальном контексте, где определена переменная var bar
= bar3
.
Однако если мы вызываем функцию из другой функции, вызываемая функция также будет использовать внешний контекст:
var bar = "bar2";
function daz(){
var bar = "bar5";
function maz(){
console.log(this.bar);
}
maz();
}
// bar2
daz();
Здесь функция daz
в качестве this.bar
использует значение переменной bar
из внешнего контекста, то есть значение глобальной переменной bar
. Функция maz также в качестве this.bar
использует значение переменной bar
из внешнего контекста, а это значение this.bar
из внешней функции daz
, которое в свою очередь представляет значение глобальной переменной bar
. Поэтому в итоге консоль выведет bar2
, а не bar5
.
Явная привязка this
С помощью методов call()
и apply()
можно задать явную привязку функции к определенному контексту:
function foo(){
console.log(this.bar);
}
var o3 = {bar: "bar3"}
var bar = "bar1";
// bar1
foo();
// bar3
foo.apply(o3);
// или
// foo.call(o3);
Во втором случае функция foo
привязывается к объекту o3
, который и определяет ее контекст. Поэтому во втором случае консоль выведет bar3
.
Метод bind
В основном, мы используем метод bind()
, чтобы вызывать функцию с указанием значения this
. Метод позволяет легко выставлять какой именно объект будет привязан к this
в момент вызова функции или метода.
Обычно нам требуется bind()
тогда, когда мы используем в методе this
и вызываем сам метод из получающего объекта. В таких случаях this
не привязывается к предполагаемому объекту, что само собой ведет к ошибке в работе кода.
В современном JavaScript у функций есть встроенный метод bind
, который позволяет зафиксировать this
. Вызов происходит с фиксированным this
:
var o = {
bar: "bar3",
foo: function () {
console.log(this.bar);
}.bind(bar),
};
var bar = "bar1";
// bar1
o.foo();
function foo() {
console.log(this.bar);
}
var bar1 = { bar: "bar1" };
var bar = "bar2";
// bar2
foo();
var func = foo.bind(bar1);
// bar1
func();
this и стрелочные функции
В стрелочных функциях объект, передаваемый через this
, берется из родительского контекста, в котором определена стрелочная функция, рассмотрим следующий пример:
const person = {
name: "Tom",
say:()=> console.log(`Меня зовут ${this.name}`)
}
// Меня зовут
person.say();
Здесь стрелочная функция say()
обращается к некому свойству this.name
, но что здесь представляет this
? Для внешнего контекста, в котором определена стрелочная функция - то есть для контекста объекта person this
представляет глобальный объект (объект окна браузера). Однако глобальной переменной name
не определено, поэтому на консоль будет выведено:
Меня зовут
Теперь немного изменим пример:
const person = {
name: "Tom",
hello(){
console.log("Привет");
let say = ()=> console.log(`Меня зовут ${this.name}`);
say();
}
}
person.hello();
Теперь стрелочная функция определена в методе hello()
. this
для этого метода представляет текущий объект person
, где определен данный метод. Поэтому и в стрелочной функции this
будет представлять объект person
, а this.name
- свойство name
этого объекта. Поэтому при выполнении программы мы получим:
Привет
Меня зовут Tom
Несмотря на то, что стрелочные функции могут добавить забот при работе this, в то же время они могут решить ряд проблем. Так, при работе с несколькими контекстами мы вынуждены учитывать, в каком контексте определяется переменная. Например, возьмем следующий код:
const school ={
title: "Oxford",
courses: ["JavaScript", "TypeScript", "Java", "Go"],
printCourses: function(){
this.courses.forEach(function(course){
console.log(this.title, course);
})
}
}
school.printCourses();
Функция printCourses
проходит по всем курсам из массива и при их выводе предваряет их значением свойства title
. Однако на консоли при запуске программы мы увидим следующее:
undefined "JavaScript"
undefined "TypeScript"
undefined "Java"
undefined "Go"
Мы видим, что значение this.title
не определено, так как this
как контекст объекта замещается глобальным контекстом. В этом случае нам надо передать подобное значение this.title
или весь контекст объекта.
const school ={
title: "Oxford",
courses: ["JavaScript", "TypeScript", "Java", "Go"],
printCourses: function(){
const that = this;
this.courses.forEach(function(course){
console.log(that.title, course);
})
}
}
school.printCourses();
Стрелочные функции также позволяют решить данную проблему:
const school ={
title: "Oxford",
courses: ["JavaScript", "TypeScript", "Java", "Go"],
printCourses: function(){
this.courses.forEach((course)=>console.log(this.title, course))
}
}
school.printCourses();
Контекстом для стрелочной функции в данном случае будет выступать контекст объекта school
. Соответственно, нам недо определять дополнительные переменые для передачи данных в функцию.