Поведение ключевого слова this зависит от контекста, в котором оно используется, и от того, в каком режиме оно используется - строгом или нестрогом.
В глобальном контексте 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 = "local"; console.log(this.bar); } var bar = "global"; foo(); // global
Если бы мы не использовали бы this
, то обращение шло бы к локальной переменной, определенной внутри функции.
function foo(){ var bar = "local"; console.log(bar); } var bar = "global"; foo(); // local
Но если бы мы использовали строгий режим (strict mode), то this в этом случае имело бы значение undefined
:
"use strict"; function foo(){ var bar = "local"; console.log(this.bar); } var bar = "global"; foo(); // ошибка - this - undefined
В контексте объекта, в том числе в его методах, ключевое слово this
ссылается на этот же объект:
const obj = { bar: "object", foo: function(){ console.log(this.bar); } } var bar = "global"; obj.foo(); // object
Код функции всегда использует в качестве this внешний контекст, в которым этот код вызывается (именно вызывается, а не определяется). Рассмотрим более сложный пример:
function foo(){ var bar = "foo_bar"; console.log(this.bar); } const obj1 = {bar:"obj1_bar", foo: foo}; const obj2 = {bar:"obj2_bar", foo: foo}; var bar = "global_bar"; foo(); // global_bar obj1.foo(); // obj1_bar obj2.foo(); // obj2_bar
Здесь определена глобальная переменная bar ("global_bar"). И также в функции foo определена локальная переменная bar ("foo_bar"). Значение какой переменной будет выводиться в функции foo? Функция foo использует определение переменной, которое определено во внешнем контексте. Для функции foo по умолчанию это глобальный контекста, поэтому она выводит значение глобальной переменной (так как данный скрипт запускается в нестрогом режиме, а значит ключевое слово this в функции foo ссылается на внешний контекст).
Иначе дело обстоит с объектами. Они определяют свой собственный контекст, в котором существует свое свойство bar. И при вызове метода foo внешним контекстом по отношению к функции будет контекст объектов obj1 и obj2.
Подобное поведение может привести к некоторому непонимаю в отдельных случаях. Так, рассмотрим другую ситуацию:
var bar = "global_bar"; const obj1 = { bar: "obj1_bar", foo: function(){ console.log(this.bar); // bar = "obj1_bar" } } const obj2 = {bar: "obj2_bar", foo: obj1.foo}; // bar = "obj2_bar" const foo = obj1.foo; // bar = "global_bar" obj1.foo(); // obj1_bar obj2.foo(); // obj2_bar foo(); // global_bar
Здесь в объекте obj1 определена функция foo:
const obj1 = { bar: "obj1_bar", foo: function(){ console.log(this.bar); // bar = "obj1_bar" } }
Эта функция foo будет брать значение для this.bar
из внешнего контекста - из объекта obj1, соответственно this.bar = "obj1_bar"
.
Объект obj2 использует функцию foo из объекта obj1:
const obj2 = {bar: "obj2_bar", foo: obj1.foo};
Однако функция obj1.foo
также будет искать значение для this.bar
опять же во внешнем котексте, а здесь это объект obj2. А в объекте obj2 это значение равно "obj2_bar".
То же самое с глобальной переменной foo
, которая ссылается на ту же функцию, что и метод obj1.foo
:
const foo = obj1.foo;
В этом случае также будет происходить поиск значения
для this.bar
, но теперь в контексте функции foo. А это глобальный контекст, где определена переменная var bar = "global_bar"
.
Если мы вызываем функцию из другой функции, вызываемая функция также будет использовать внешний контекст:
var bar = "global bar"; function foo(){ var bar = "foo bar"; function moo(){ console.log(this.bar); } moo(); } foo(); // global bar
Здесь функция foo()
в качестве this.bar
использует значение переменной bar из внешнего контекста, то есть значение глобальной переменной bar.
Функция moo()
также в качестве this.bar
использует значение переменной bar из внешнего контекста, то есть this.bar
для внешней функции foo,
которое в свою очередь представляет значение глобальной переменной bar. Поэтому в итоге консоль выведет "global bar", а не "foo bar".
С помощью методов call()
и apply()
можно задать явную привязку функции к определенному контексту:
function foo(){ console.log(this.bar); } var obj = {bar: "obj_bar"} var bar = "global_bar"; foo(); // global_bar foo.apply(obj); // obj_bar // или // foo.call(obj);
Во втором случае функция foo привязывается к объекту obj, который и определяет ее контекст. Поэтому во втором случае консоль выведет "obj_bar".
Метод f.bind(o)
позволяет создать новую функцию с тем же телом и областью видимости, что и функция f, но с привязкой к объекту o:
function foo(){ console.log(this.bar); } const obj = {bar: "object"} var bar = "global"; foo(); // global const func = foo.bind(obj); func(); // object
В стрелочных функциях в качестве 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(){ 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(){ 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(){ this.courses.forEach((course)=>console.log(this.title, course)) } } school.printCourses();
Контекстом для стрелочной функции в данном случае будет выступать контекст объекта school. Соответственно, нам не надо определять дополнительные переменые для передачи данных в функцию.