Сейчас мы разберем все нюансы работы с this. Многие вещи вам должны быть уже известны, но не лишним будет их повторить.

Работа с this

Пусть у нас есть какая-то функция func, внутри которой используется this:

function func() {
	alert(this.value);
}

На что указывает this в этой функции? А мы не знаем. И JavaScript не знает. И сама функция не знает. То есть: в момент создания функции на что именно указывает this не определено. И определится это только тогда, когда эта функция будет вызвана.

Давайте теперь сделаем инпут и привяжем к нему нашу функцию func. Теперь this указывает на наш инпут:

<input id="elem" value="привет">
var elem = document.getElementById('elem');
elem.addEventListener('blur', func);
function func() {
	alert(this.value); //выведет 'привет'
}

Но ведь у нас может быть не один инпут, а несколько, и каждому мы можем привязать нашу функцию func. В этом случае для первого элемента this в функции будет указывать на него, а для второго - на него:

<input id="elem1">
<input id="elem2">
var elem1 = document.getElementById('elem1');
elem1.addEventListener('blur', func); //тут this - это первый элемент

var elem2 = document.getElementById('elem2');
elem2.addEventListener('blur', func); //тут this - это второй элемент

function func() {
	alert(this.value); //а тут this не еще определен
}

А что будет, если в функции указать this, но не привязать ее ни к какому элементу, вот так:

function func() {
	console.log(this);
}

func();

В этом случае в новом стандарте JavaScript там будет лежать undefined, а в старом - объект window.

Давайте подведем итог

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

Функция в функции

Пусть у нас есть функция в функции. Пусть внешняя функция называется func, а внутренняя - child. Если задать какую-либо переменную во внешней функции - она будет доступна во внутренней:

function func() {
	var test = 'привет';

	function child() {
		alert(test); //выведет 'привет'
	}
	child();
}

Пусть теперь наша функция func привязана к инпуту и this в ней указывает на инпут:

<input id="elem" value="привет">
var elem = document.getElementById('elem');
elem.addEventListener('blur', func);

function func() {
	alert(this.value); //выведет 'привет'

	function child() {
		alert(this.value); //выдаст ошибку!
	}
	child();
}

Внутри функции child мы пытаем обратиться к инпуту через this, но у нас это не получится, потому что контекст у этой функции другой и this не указывает на инпут.

Получается, что переменные функции func видны в функции child, но this у них разный.

Популярный способ решения данной проблемы такой: во внешней функции запишем this в любую переменную (например, в self) и эта переменная будет доступна в child, как и все переменные. Таким образом мы передадим this из внешней функции во внутреннюю:

function func() {
	alert(this.value);
	var self = this; //запишем this в любую переменную

	function child() {
		alert(self.value); //все работает, так как self тут доступна
	}
	child();
}

Проблемы такого плана поджидают нас в самых неожиданных местах. Пусть сейчас функция func сработает по клику на инпут и внутри функции запустится таймер, который каждую секунду будет алертом выводить value нашего инпута:

function func() {
	window.setInterval(function() {
		alert(this.value); //не будет работать
	}, 1000);
}

Это не будет работать, так как мы потеряли контекст. Ведь this размещен во внутренней анонимной функции и поэтому он не указывает на наш инпут.

Поправим проблему введением self:

function func() {
	var self = this;
	window.setInterval(function() {
		alert(self.value);
	}, 1000);
}

Проблему также можно поправить через стрелочные функции ES6 (мы их проходили в этом уроке). В этом случае this внутри стрелочной функции такой же, как и this снаружи:

function func() {
	window.setInterval(() => alert(this.value), 1000);
}

Привязывание контекста

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

Таких методов существует три: метод call, метод apply и метод bind. Давайте разбираться с ними.

Метод call

Пусть у нас есть инпут #elem. Давайте получим ссылку на него с помощью getElementById и запишем ее в переменную elem:

<input id="elem" value="привет">
var elem = document.getElementById('elem');

Давайте теперь сделаем функцию func, внутри которой алертом выведем this.value:

function func() {
	alert(this.value);
}

Пока наша функция не знает, на что ссылается this. Вот, если бы мы ее привязали через addEventListener, тогда да. Но мы не будем этого делать. В замен мы просто вызовем нашу функцию, сказав ей, что this должен быть равен elem.

Это делается вот так: func.call(elem). Этот код эквивалентен простому вызову функции func вот так: func(), только с условием, что this равен elem.

Итак, синтаксис метода call такой: функция.call(объект, который записать в this).

Давайте соберем все вместе:

<input id="elem" value="привет">
var elem = document.getElementById('elem');

function func() {
	alert(this.value);
}
func.call(elem); //выведет value инпута

Метод call с параметрами

Пусть теперь функция func принимает некоторые параметры, назовем их param1 и param2:

function func(param1, param2) {
	alert(this.value + param1 + param2);
}

При вызове функции через call можно передать эти параметры вот так:

func.call(elem, param1, param2);

То есть полный синтаксис метода call такой: функция.call(контекст, параметр1, параметр2 и так далее).

Метод apply

Кроме метода call, существует очень похожий метод apply - разница в способе передачи параметров. Следующие две записи эквивалентны:

func.call(elem, param1, param2);
func.apply(elem, [param1, param2]);

То есть в apply параметры передаются в виде массива, а не перечисляются через запятую, как в методе call. Вот и все отличие. В зависимости от задачи бывает удобен то один, то другой метод.

Метод bind

Следующий метод bind позволяет привязать контекст к функции навсегда. Он вызывается вот так: func.bind(elem), но результатом работы будет не результат функции func, а новая функция, которая такая же, как и func, но у нее this всегда указывает на elem.

Давайте посмотрим на примере. Пусть у нас есть следующая функция func:

function func(param1, param2) {
	alert(this.value + param1 + param2);
}

С помощью bind скажем, что this в ней всегда равен elem и запишем в новую переменную:

var newFunc = func.bind(elem); //обратите внимание - параметры не передаем

Теперь в переменной newFunc лежит функция. Давайте вызовем ее, передав в первый параметр '!', а во второй '?' (напоминаю, что в elem лежит инпут с value, равным 'привет'):

newFunc('!', '?'); //выведет 'привет!?'

Давайте соберем все вместе:

<input id="elem" value="привет">
var elem = document.getElementById('elem');

function func(param1, param2) {
	alert(this.value + param1 + param2);
}

var newFunc = func.bind(elem);
newFunc('!', '?'); //выведет 'привет!?'

Не обязательно записывать результат работы bind в новую функцию newFunc, можно просто перезаписать func:

var func = func.bind(elem);

Теперь func - такая же функция, как и была, но с жестко связанным this.