Дизайн сайтовЮзабилитиВерстка сайтовВходящий маркетингКонверсия сайтовПоисковые системыКонтекстная рекламаИнформационные технологииНовости E-Planet
27 мая 2015

Заключительные шаги в овладении ключевым словом «this»

Большинство тех, кто имеет дело с JavaScript, владеет базовыми навыками работы с ключевым словом this. Обычно решающим фактором, определяющим особенности и нюансы использования этого слова, является текущий контекст выполнения. Тем не менее, задача определения контекста может быть довольно непростой, особенно если контекст получает неожиданные изменения в ходе выполнения программы. В этой статье мы рассмотрим некоторые случаи, в которых это может произойти, и расскажем, как этого можно избежать.

Общие вопросы

В этом разделе мы рассмотрим некоторые из наиболее распространенных проблем, возникающих в связи с использованием this, и узнаем, как их исправить.

1. Использование this при выделении метода

Одна из самых распространенных ошибок, которую совершают программисты, пытающиеся присвоить метод объекту ожидание, что this будет указывать на исходный объект. Как видно из примера ниже, такой подход просто не работает:

var car = {
brand: \"Nissan\",
getBrand: function(){
  console.log(this.brand);
}
};
var getCarBrand = car.getBrand;
getCarBrand();  // результат: undefined

Даже если вам кажется, что getCarBrand - это ссылка на car.getBrand(),на самом деле это всего лишь еще одна ссылка на getBrand(). В итоге получается, что getCarBrand() это простая функция, с общим контекстом вызова. Для того чтобы убедиться в том, что getCarBrand не привязан к конкретному объекту, просто добавьте в конце кода alert(getCarBrand) и вы получите:

function(){
console.log(this.brand);
}

getCarBrand содержит только простую функцию, которая больше не является методом объекта car. Таким образом, this.brand фактически вызывает window.brand , который, естественно, не определен. Если мы пытаемся извлечь метод из объекта, он становится простой функцией. Его связь с объектом разорвана, и он больше не работает, как предполагалось.

Так как мы можем исправить это? Если мы хотим сохранить ссылку на исходный объект, мы должны явно привязать функцию getBrand () к объекту Car, назначив его переменной getCarBrand. Мы можем сделать это с помощью метода bind.

var getCarBrand = car.getBrand.bind(car);
getCarBrand();  // вывод: Nissan

Теперь мы получаем правильный результат, потому что успешно переопределили контекст вызова функции.

2. Функции обратного вызова (Callback )

Следующая проблема возникает, когда мы переходим к методу (который использует 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 (), которая присвоена кнопке объекта.Передача параметра в функции это неявное назначение, так что здесь происходит почти то же самое, что и в предыдущем примере. Иными словами, когда мы выполнили метод для объекта, отличающегося от объекта, для которого этот метод был первоначально определен, ключевое слово this больше не относится к исходному объекту, а указывает на объект, который вызывает метод.

Если мы хотим сохранить ссылку на исходный объект неповрежденной, мы должны явно связать getBrand() с объектом car при помощи метода bind.

el.addEventListener(\"click\", car.getBrand.bind(car));

3. Замыкания

Другой случай, когда this может ошибиться с контекстом использование его внутри замыканий. Рассмотрим следующий пример:

var car = {
brand: \"Nissan\",
getBrand: function(){
  var closure = function(){
   console.log(this.brand);
  };
  return closure();
}
};
car.getBrand();  // вывод: undefined 

Мы получаем undefined, потому что замыкание функции (внутренняя функция) не имеет доступа к переменной внешних функций. В итоге получается, что 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();  // вывод: 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 

ECMAScript 6 нас спасет

В предыдущем примере мы увидели пример того, что известно как \"лексический this\" - когда мы присваиваем this переменной. В ECMAScript 6 мы можем использовать аналогичную, но более изящную технику с помощью новых стрелочных функций.

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

- Когда функция вызывается с новым оператором, this относится к вновь созданному объекту;

- Когда используется стрелочная функция (введены в ECMAScript 6), this зависит от лексической области и относится к родительскому объекту.

По материалам www.sitepoint.com