В предыдущей статье мы узнали основы правильного использования 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 вызовет у вас головную боль.