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

Последнее обновление: 24.10.2023

Поведение ключевого слова 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 = "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".

Метод bind

Метод 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 и стрелочные функции

В стрелочных функциях в качестве 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. Соответственно, нам не надо определять дополнительные переменые для передачи данных в функцию.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850