Ключевое слово this
Поведение ключевого слова this зависит от контекста, в котором оно используется, и от того, в каком режиме оно используется - строгом или нестрогом.
Глобальный контекст
В глобальном контексте this ссылается на глобальный объект. В данном случае поведение не зависит от режима (строгий или нестрогий):
this.alert("global alert");
this.console.log("global console");
var currentDocument = this.document;
Но в данном случае использвание this избыточно.
Контекст функции
Если скрипт запускается в строгом режиме (директива "use strict"), то this ссылается непосредственно на контекст функции. Иначе this ссылается на внешний контекст. Например:
function foo(){
var bar = "bar2";
console.log(this.bar);
}
var bar = "bar1";
foo(); // bar1
Вообщем-то смысла использования this в данном случае нет, так как мы могли бы напрямую использовать локальную переменную.
Если бы мы использовали строгий режим, то this в этом случае имело бы значение undefined:
"use strict";
function foo(){
var bar = "bar2";
console.log(this.bar);
}
var bar = "bar1";
foo(); // ошибка - this - undefined
Контекст объекта
В контексте объекта, в том числе в его методах, ключевое слово this ссылается на этот же объект:
var o = {
bar: "bar3",
foo: function(){
console.log(this.bar);
}
}
var bar = "bar1";
o.foo(); // bar3
Примеры
Рассмотрим более сложный пример:
function foo(){
var bar = "bar2";
console.log(this.bar);
}
var o3 = {bar:"bar3", foo: foo};
var o4 = {bar:"bar4", foo: foo};
var bar = "bar1";
foo(); // bar1
o3.foo(); // bar3
o4.foo(); // bar4
Здесь определена глобальная переменная 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;
o1.foo(); // bar1
o2.foo(); // bar2
foo(); // bar3
Несмотря на то, что объект 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();
}
daz(); // bar2
Здесь функция daz в качестве this.bar использует значение переменной bar из внешнего контекста, то есть значение глобальной переменной bar. Функция maz также в качестве this.bar использует значение переменной bar из внешнего контекста, а это значение this.bar из внешней функции daz, которое в свою очередь представляет значение глобальной переменной bar. Поэтому в итоге консоль выведет "bar2", а не "bar5".
Явная привязка
С помощью методов call() и apply() можно задать явную привязку функции к определенному контексту:
function foo(){
console.log(this.bar);
}
var o3 = {bar: "bar3"}
var bar = "bar1";
foo(); // bar1
foo.apply(o3); // bar3
// или
// foo.call(o3);
Во втором случае функция foo привязывается к объекту o3, который и определяет ее контекст. Поэтому во втором случае консоль выведет "bar3".
Метод bind
Метод f.bind(o) позволяет создать новую функцию с тем же телом и областью видимости, что и функция f, но с привязкой к объекту o:
function foo(){
console.log(this.bar);
}
var o3 = {bar: "bar3"}
var bar = "bar1";
foo(); // bar1
var func = foo.bind(o3);
func(); // bar3
this и стрелочные функции
При работе с несколькими контекстами мы вынуждены учитывать, в каком контексте определяется переменная. Например, возьмем следующий код:
var 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 или весь контекст объекта.
var school ={
title: "Oxford",
courses: ["JavaScript", "TypeScript", "Java", "Go"],
printCourses: function(){
var that = this;
this.courses.forEach(function(course){
console.log(that.title, course);
})
}
}
school.printCourses();
Output
Стрелочные функции также позволяют решить данную проблему:
var school ={
title: "Oxford",
courses: ["JavaScript", "TypeScript", "Java", "Go"],
printCourses: function(){
this.courses.forEach((course)=>console.log(this.title, course))
}
}
school.printCourses();
Контекстом для стрелочной функции в данном случае будет выступать контекст объекта school. Соответственно, нам недо определять дополнительные переменые для передачи данных в функцию.