Вступление

В отличие от других языков, одним из самых запутанных механизмов в JavaScript является this keyword. Это специальный идентификатор, который автоматически определяется в рамках каждой функции, но то, к чему он конкретно относится, иногда беспокоит даже опытных разработчиков JavaScript. Механизм this JavaScript на самом деле не так уж и сложен для понимания, но разработчики цитируют его как сложный или запутанный в собственном уме, не пытаясь понять его.

Теперь вам может быть интересно, если механизм this так сбивает с толку даже опытных разработчиков JavaScript, почему он вообще полезен? Это больше хлопот, чем того стоит? Итак, прежде чем мы перейдем к что и как, давайте посмотрим, почему.

Почему это?

Давайте попробуем понять мотивацию и полезность this:

function identifyName() {
	return this.name.toUpperCase();
}

function speakName() {
	var greeting = "Hello, I'm " + identifyName.call( this );
	console.log( greeting );
}
var me = {
	name: "Sam"
};

var you = {
	name: "David"
};

identifyName.call( me ); // SAM
identifyName.call( you ); // DAVID

speakName.call( me ); // Hello, I'm SAM
speakName.call( you ); // Hello, I'm DAVID

Если вас смущает как из приведенного выше фрагмента кода, не волнуйтесь, я займусь этим. Давайте отложим эти вопросы на время, чтобы мы могли более четко сосредоточиться на почему.

Этот фрагмент кода позволяет повторно использовать функции identifyName() и speakName() с несколькими объектами контекста (me и you), вместо того, чтобы разделять версию функции для каждого объекта.

Вместо того, чтобы полагаться на this, мы могли бы явно передать объект контекста как identifyName(), так и speakName().

function identifyName(context) {
	return context.name.toUpperCase();
}

function speakName(context) {
	var greeting = "Hello, I'm " + identifyName( context );
	console.log( greeting );
}

identifyName( you ); // DAVID
speakName( me ); // Hello, I'm SAM

Однако, если мы сравним два фрагмента, механизм this обеспечивает более структурированный способ неявной передачи ссылки на объект, что, в свою очередь, приводит к более чистому дизайну API и упрощению повторного использования.

Чем сложнее ваше использование, тем яснее вы начнете понимать, что передача контекста в качестве явного параметра приводит к более запутанному коду, чем передача this контекста.

Давайте разберемся с некоторыми заблуждениями о том, что this на самом деле не работает.

Устранение заблуждений и заблуждений по этому поводу

Сам

  • Первое распространенное неверное толкование - это предположение, что this относится к самой функции.
  • Давайте еще раз обдумаем вышеприведенное утверждение. Зачем вам обращаться к функции изнутри? Наиболее частой причиной, которую мы могли бы получить, была рекурсия (вызов функции изнутри).
  • Посмотрите на пример и убедитесь, что this не позволяет функции получить ссылку на себя, как мы могли предположить.

Рассмотрим следующий код, в котором мы пытаемся подсчитать, сколько раз вызывалась функция counter:

function counter(num) {
	console.log( "counter: " + num );
	// keep track of how many times `counter` is called
	this.count++;
}

counter.count = 0;

var i;

for (i=0; i<10; i++) {
	if (i > 5) {
		counter( i );
	}
}
// counter: 6
// counter: 7
// counter: 8
// counter: 9

// how many times was `counter` called?
console.log( counter.count ); // 0 -- WTH! How did this happen?

counter.count по-прежнему 0, хотя мы видели, что четыре оператора console.log ясно указывают на то, что counter(...) на самом деле был вызван четыре раза.

Когда код выполняет counter.count = 0, он действительно добавляет свойство count к объекту функции counter. Но для ссылки this.count внутри функции this на самом деле вообще не указывает на этот объект функции, и поэтому, хотя имена свойств одинаковы, корневые объекты разные, и, таким образом, путаница остается.

Примечание. Ответственный разработчик должен теперь спросить себя: «Если я увеличивал count property, но это не то, что я ожидал, то какое count я увеличивал». Если копнуть глубже, можно понять, что он / она создал глобальную переменную count, которая в настоящее время имеет значение Nan.

Некоторые разработчики вообще избегали бы этой проблемы и использовали бы какое-то другое решение, подобное приведенному ниже, или что-то еще:

function counter(num) {
	console.log( "counter: " + num );

	// keep track of how many times `counter` is called
	data.count++;
}

var data = {
	count: 0
};

var i;

for (i=0; i<10; i++) {
	if (i > 5) {
		foo( i );
	}
}
// counter: 6
// counter: 7
// counter: 8
// counter: 9

// how many times was `counter` called?
console.log( data.count ); // 4

Хотя это правда, что этот подход решает проблему, к сожалению, он просто игнорирует реальную проблему - непонимание того, что this означает и как это работает, - и вместо этого возвращается в зону комфорта более знакомого механизма: лексическая область .

Вместо того, чтобы избегать this, мы принимаем его.

Его Сфера

  • Следующее наиболее распространенное заблуждение относительно значения this состоит в том, что он каким-то образом относится к области видимости функции. Это определенно сложный вопрос, потому что с одной стороны есть доля правды, но с другой стороны, это совершенно неверно.
  • Для ясности: this не относится к лексической области видимости функции. Верно, что внутри область видимости похожа на объект со свойствами для каждого из доступных идентификаторов. Но объект области видимости недоступен для кода JavaScript.

Рассмотрим код, который пытается (и терпит неудачу!) Пересечь границу, и использует this для неявной ссылки

function foo() {
	var a = 2;
	this.bar();
}

function bar() {
	console.log( this.a );
}

foo(); //undefined

Делается попытка ссылаться на функцию bar() через this.bar(). Это почти наверняка случайность, что это работает. Самым естественным способом вызвать bar() было бы опустить начальный this. и просто сделать лексическую ссылку на идентификатор.

Однако разработчик, написавший такой код, пытается использовать this для создания моста между лексическими областями видимости foo() и bar(), чтобы bar() имел доступ к переменной a во внутренней области видимости foo(). Такой мост невозможен. Вы не можете использовать this ссылку для поиска чего-либо в лексической области. Это невозможно.

Каждый раз, когда вы чувствуете, что пытаетесь смешивать поиск в лексической области видимости с this, напоминайте себе: моста нет.

Отбросив различные неверные предположения, давайте, наконец, обратим внимание на то, что this на самом деле и как на самом деле работает механизм this.

Как я сказал ранее, this - это не привязка времени компиляции, а привязка времени выполнения. Это зависит от условий вызова функции. this привязка не имеет ничего общего с тем, где объявлена ​​функция, а имеет все отношение к способу вызова функции.

Когда функция вызывается, создается контекст выполнения. Эта запись содержит информацию о том, откуда была вызвана функция (стек вызовов), как была вызвана функция, какие параметры были переданы и т. Д. Одним из свойств этой записи является ссылка this, которая будет использоваться на время выполнения этой функции.

Давайте рассмотрим несколько простых примеров, чтобы понять основную идею:

var myVar = 100;

function WhoIsThis() {
    var myVar = 200;

    console.log("myVar = " + myVar); // 200
    console.log("this.myVar = " + this.myVar); // 100
}
WhoIsThis(); // inferred as window.WhoIsThis()

В приведенном выше примере функция WhoIsThis() вызывается из глобальной области видимости. Глобальная область видимости означает в контексте оконного объекта. При желании мы можем назвать это как window.WhoIsThis(). Итак, в приведенном выше примере ключевое слово this в функции WhoIsThis() будет относиться к объекту окна. Итак, this.myVar вернет 100. Однако, если вы обращаетесь к myVar без this, тогда он будет ссылаться на локальную переменную myVar, определенную в функции WhoIsThis().

В строгом режиме значение this не будет определено в глобальной области видимости.

Что нужно иметь в виду:

  • this привязка - постоянный источник путаницы для разработчика JavaScript, который не тратит время на изучение того, как на самом деле работает механизм.
  • Предположения, метод проб и ошибок и слепое копирование и вставка из ответов Stack Overflow не являются эффективным или правильным способом использования этого важного механизма this.
  • Чтобы узнать this, вы сначала должны узнать, что this не, несмотря на любые предположения или заблуждения, которые могут привести вас по этому пути. this не является ни ссылкой на саму функцию, ни ссылкой на лексическую область видимости функции.
  • this на самом деле является привязкой, которая создается при вызове функции, и то, на что она ссылается, полностью определяется сайтом вызова, на котором вызывается функция.

В следующей статье я попытаюсь объяснить this гораздо более подробно, а также то, как найти сайт вызова функции, чтобы определить, как ее выполнение будет связывать this. Мы также рассмотрим, как this ведет себя в различных сценариях, например, в глобальном контексте, внутри функции или с помощью таких методов, как call (), apply () или bind () с подходящими примерами. Следите за обновлениями!