Вступление
В отличие от других языков, одним из самых запутанных механизмов в 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 () с подходящими примерами. Следите за обновлениями!