Статьи

Последние шаги в освоении JavaScript-слова «this»

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