В предыдущей статье мы узнали основы правильного использования this
ключевого слова в JavaScript. Мы увидели, что решающим фактором в определении того, к чему this
относится, является выяснение текущего контекста выполнения. Тем не менее, эта задача может быть немного сложнее в ситуациях, когда контекст изменяется так, как мы этого не ожидаем. В этой статье я расскажу, когда это может произойти и что мы можем сделать, чтобы это исправить.
Исправление общих проблем
В этом разделе мы рассмотрим некоторые наиболее распространенные проблемы, возникающие при использовании ключевого слова this
и узнаем, как их исправить.
1. Использование this
в извлеченных методах
Одна из наиболее распространенных ошибок, которые делают люди, — это когда они пытаются присвоить метод объекта переменной и ожидают, что this
все равно будет указывать на исходный объект. Как видно из следующего примера, это просто не работает.
var car = { brand: "Nissan", getBrand: function(){ console.log(this.brand); } }; var getCarBrand = car.getBrand; getCarBrand(); // output: undefined
Хотя getCarBrand
является ссылкой на car.getBrand()
, на самом деле это просто еще одна ссылка на сам getBrand()
. Мы уже знаем, что call-site — это то, что имеет значение при определении контекста, и здесь call-site — это getCarBrand()
, который представляет собой простой и простой вызов функции.
Чтобы доказать, что getCarBrand
указывает на безосновательную функцию (которая не привязана к какому-либо конкретному объекту), просто добавьте alert(getCarBrand);
в нижней части кода, и вы увидите следующий вывод:
function(){ console.log(this.brand); }
getCarBrand
содержит просто обычную функцию, которая больше не является методом car
объекта. Таким образом, в этом случае this.brand
фактически переводится в window.brand
, который, конечно, undefined
.
Если мы извлекаем метод из объекта, он снова становится простой функцией. Его связь с объектом разорвана, и он больше не работает, как задумано. Другими словами, извлеченная функция не привязана к объекту, из которого она была взята.
Итак, как мы можем исправить это? Что ж, если мы хотим сохранить ссылку на исходный объект, нам нужно явно привязать getBrand()
к объекту car
когда мы назначим ее переменной getCarBrand
. Мы можем сделать это с помощью метода bind () .
var getCarBrand = car.getBrand.bind(car); getCarBrand(); // output: Nissan
Теперь мы получаем правильный вывод, потому что мы успешно переопределяем контекст так, как мы хотим.
2 this
используется в обратных вызовах
Следующая проблема возникает, когда мы передаем метод (который использует this
в качестве параметра) для использования в качестве функции обратного вызова. Например:
<button id="btn" type="button">Get the car's brand</button> var car = { brand: "Nissan", getBrand: function(){ console.log(this.brand); } }; var el = document.getElementById("btn"); el.addEventListener("click", car.getBrand);
Даже если мы используем car.getBrand
, мы на самом деле получаем только функцию getBrand()
которая прикреплена к объекту button
.
Передача параметра в функцию является неявным присваиванием, поэтому то, что здесь происходит, почти такое же, как в предыдущем примере. Разница в том, что теперь car.getBrand
не явно, а неявно. И результат в значительной степени тот же — мы получаем простую функцию, привязанную к объекту button
.
Другими словами, когда мы выполняем метод для объекта, который отличается от объекта, для которого метод был первоначально определен, ключевое слово this
больше не ссылается на исходный объект, а на объект, который вызывает метод.
Со ссылкой на наш пример: мы выполняем car.getBrand
на el
(элемент button), а не на объекте car
, для которого он был первоначально определен. Следовательно, this
больше не относится к car
, а скорее к el
.
Если мы хотим сохранить ссылку на исходный объект без изменений, нам снова нужно явно привязать getBrand()
к объекту car
с помощью метода bind()
.
el.addEventListener("click", car.getBrand.bind(car));
Теперь все работает как положено.
3 this
используется внутри крышки
Другой случай, когда this
контекст может быть ошибочным, — это когда мы используем this
внутри замыкания . Рассмотрим следующий пример:
var car = { brand: "Nissan", getBrand: function(){ var closure = function(){ console.log(this.brand); }; return closure(); } }; car.getBrand(); // output: undefined
Здесь вывод, который мы получаем, не undefined
, потому что функции замыкания (внутренние функции) не имеют доступа к this
переменной внешних функций. this.brand
результатом является то, что this.brand
равен window.brand
, потому что this
во внутренних функциях связано с глобальным объектом.
Чтобы исправить эту проблему, мы должны оставить this
привязанным к функции getBrand()
.
var car = { brand: "Nissan", getBrand: function(){ var closure = function(){ console.log(this.brand); }.bind(this); return closure(); } }; car.getBrand(); // output: Nissan
Эта привязка эквивалентна car.getBrand.bind(car)
.
Другой популярный метод исправления замыканий — присвоение значения this
другой переменной, что предотвращает нежелательные изменения.
var car = { brand: "Nissan", getBrand: function(){ var self = this; var closure = function(){ console.log(self.brand); }; return closure(); } }; car.getBrand(); // output: Nissan
Здесь значение this
может быть присвоено _this
, that
self
, me
, my
, context
, псевдо-имя объекта или что-либо еще, что работает для вас. Главное — сохранить ссылку на исходный объект.
ECMAScript 6 на помощь
В предыдущем примере мы видели учебник по так называемому «лексическому this
» — когда мы устанавливаем значение this
в другую переменную. В ECMAScript 6 мы можем использовать похожую, но более элегантную технику, применимую через новые функции стрелок.
Arrow-функции создаются не ключевым словом function
, а так называемым оператором «жирной стрелки» ( =>
). В отличие от обычных функций, функции-стрелки получают значение this
из своей непосредственной области действия. Лексическая привязка функции стрелки не может быть переопределена, даже с new
оператором.
Давайте теперь посмотрим, как можно использовать функцию стрелки для замены var self = this;
заявление.
var car = { brand: "Nissan", getBrand: function(){ // the arrow function keeps the scope of "this" lexical var closure = () => { console.log(this.brand); }; return closure(); } }; car.getBrand(); // output: Nissan
Что нужно помнить об this
Мы увидели, что ключевое слово this
, как и любой другой механизм, следует некоторым простым правилам, и, если мы хорошо их знаем, мы можем использовать этот механизм с большей уверенностью. Итак, давайте быстро вспомним то, что мы узнали (из этой и из предыдущей статьи):
-
this
относится к глобальному объекту в следующих случаях:- во внешнем контексте, вне любого функционального блока
- в функциях, которые не являются методами объектов
- в функциях, которые не являются конструкторами объектов
- Когда функция вызывается как свойство родительского объекта,
this
относится к родительскому объекту. - Когда функция вызывается с помощью
call()
apply()
илиbind()
,this
относится к первому аргументу, переданному этим методам. Если первый аргументnull
или не является объектом,this
относится к глобальному объекту. - Когда функция вызывается с оператором
new
,this
относится к вновь созданному объекту. - Когда используется функция стрелки (введенная в ECMAScript 6),
this
опирается на лексическую область и ссылается на родительский объект.
Зная эти простые и понятные правила, мы можем легко предсказать, на что this
будет указывать, и если это не то, чего мы хотим, мы знаем, какие методы мы можем использовать, чтобы это исправить.
Резюме
this
ключевое слово в JavaScript — сложная концепция для освоения, но при достаточной практике освоить ее вы сможете. Я надеюсь, что эта статья и моя предыдущая статья послужат хорошей основой для вашего понимания и окажутся ценным справочным материалом в следующий раз, когда this
вызовет у вас головную боль.