Раскрытие тайны this в JavaScript
В этой статье я хочу пролить свет на использование this
в JavaScript и
привнести немного ясности и понимание о том, как this
работает. Это знание
не относится к чёрной магии и не является запретным, наоборот — понимание
механизма работы this
чрезвычайно полезно для всех JavaScript-разработчиков.
Вдохновение для статьи я почерпнул из последней главы моей будущей книги
Архитектура приложения на JavaScript, которую вы, кстати, можете
заказать уже сейчас. В упомянутой выше главе я в том числе описываю то,
как работают зоны видимости в JavaScript.
Пока вы до конца не осознаете this
, вы скорее всего будете чувствовать
себя так:
Это безумие, верно? В этой короткой статье я постараюсь прояснить сложившуюся ситуацию.
Как это работает
Если метод был вызван из объекта, тогда this
в контексте метода является
ссылкой на родительский объект.
var parent = {
method: function () {
console.log(this);
}
};
parent.method(); // ссылается на родительский объект
Заметьте, что этот паттерн очень уязвим, так как при вызове метода по ссылке
this
не будет больше ссылаться на родительский объект parent
. Вместо
этого, this
теперь будет ссылаться на глобальный объект Window
.
Это обстоятельство приводит в замешательство большинство разработчиков.
var parentless = parent.method;
parentless(); // ссылается на Window
В последней строчке примера вы должны обратить внимание на то, как именно
вызывается функция - возможны два варианта. Либо функция вызывается как
свойство объекта, либо она сама по себе. И если она вызывается как свойство,
то this
будет ссылаться на данное свойство, в противном случае — на
глобальный объект. В данном примере это Window
, но в строгом режиме
this
будет возвращать undefined
.
В случае с конструктором this
ссылается на созданный экземпляр при условии
использования ключевого слова new
.
function ThisClownCar () {
console.log(this);
}
new ThisClownCar(); // ссылается на ThisClownCar {}
Стоит заметить, что поведение this
в конструкторе весьма неочевидное: если
вы случайно забудете дописать new
, то this
опять будет ссылаться на
глобальный объект, как мы уже наблюдали в примере с parentless
.
ThisClownCar();
// ссылается на Window
Ручное управление над this
Методы .call
, .apply
, и .bind
используются для управляемого вызова
функций, помогая определять оба передаваемых значения — this
, он же контекст
функции, и arguments
.
Метод Function.prototype.call
может принимать любое количество аргументов.
Первый из них будет использоваться как this
, а оставшиеся будут переданы
вызываемой функции как аргументы.
Array.prototype.slice.call([1, 2, 3], 1, 2)
// ссылается на [2]
Метод Function.prototype.apply
очень похож на предыдущий, только аргументы в
нём передаются одним массивом, вместо неопределённого количеств аргументов,
как в методе call
.
String.prototype.split.apply('13.12.02', ['.'])
// ссылается на ['13', '12', '02']
Метод Function.prototype.bind
возвращает специальную функцию, которая, в
свою очередь, может быть использована для вызова еще одной функции. От её
имени и будет вызван bind
. Функция всегда будет использовать переданный ей
this
, и в тоже время, ей можно передать несколько аргументов, в качестве
всегда используемых в возвращаемой функции. Это может быть очень удобно для
каррирования оригинальной функции.
var arr = [1, 2];
var add = Array.prototype.push.bind(arr, 3);
// эквивалентно arr.push(3)
add();
// эквивалентно arr.push(3, 4)
add(4);
console.log(arr);
// ссылается на [1, 2, 3, 3, 4]
Область видимости и this
В следующем случае this
будет неизменным в разных областях видимости. Это
исключение в правиле и оно часто приводит к ошибкам среди начинающих
разработчиков.
function scoping () {
console.log(this);
return function () {
console.log(this);
};
}
scoping()();
// ссылается на Window
// ссылается на Window
Распространённый способ обойти создавшуюся проблему — это создать локальную
переменную, содержащую ссылку на this
в текущей функции, а значит, и в ее
области видимости. Она останется доступной во вложенной функции. Вложенная
функция, в свою очередь, будет иметь свою собственную переменную this
, что
означает невозможность использования this
родительской функции напрямую.
function retaining () {
var self = this;
return function () {
console.log(self);
};
}
retaining()();
// ссылается на Window
Если вы всё-таки по неизвестным мне причинам захотите использовать
родительский this
в качестве контекста для вложенной функции, то
я могу посоветовать вам предпочитаемый лично мною метод с использованием
функции .bind
. Эта функция может быть использована в том числе для того,
чтобы пробросить родительский контекст во вложенную функцию.
function bound () {
return function () {
console.log(this);
}.bind(this);
}
bound()();
// ссылается Window
Вопросы?
Осталось ли для вас что-то неясное в рамках этой статьи? Стал ли вам хоть
немного понятней механизм работы this
? Сообщите мне, если на ваш взгляд
я пропустил какие-то важные ситуации или красивые решения.
Если вам понравился этот пост, то возможно вам стоит вам присмотреться к моей будущей книге «Архитектура приложения на JavaScript». Вы можете заказать раннее издание уже сейчас.