Руководство по this в JavaScript
Перевод статьи Ashay Mandwarya: A guide to this in JavaScript
Статья для новичков в JavaScript, разъясняющая что такое this и какие могут быть особенности его использования.
Ключевое слово this является одним из наиболее широко используемых и все же не всегда правильно понимаемых в JavaScript. В этой статье я расскажу о всех аспектах использования this.
Давайте вернемся к старым добрым школьным дням, когда мы изучали что такое местоимения. К примеру возьмем фразу: Филип плывет быстро, потому что он хочет выиграть гонку. Обратите внимание на использование местоимения «он». Мы не обращаемся непосредственно к Филипу, а используем местоимение он для обозначения Филипа. Точно так же JavaScript использует ключевое слово this в качестве референта для ссылки на контекст объекта.
Пример:
var car = { make: "Lamborghini", model: "Huracán", fullName: function () { console.log(this.make + " " + this.model); console.log(car.make + " " + car.model); } } car.fullName();
В приведенном выше коде у нас есть объект car, который имеет свойства make, model и fullName. Значение fullName — это функция, которая печатает полное имя автомобиля, используя 2 разных синтаксиса.
- Используя this => this.make + » » + this.model она ссылается на в контекст объекта, который является car, так что this.make фактически является car.make.
- Используя «точечную» запись, мы можем получить доступ к свойствам объектов, car.make и car.model.
this
Теперь, когда мы поняли, что такое this и как оно используется, давайте сформируем несколько практических правил.
this
это ключевое слово относящаяся к объекту, к которому оно принадлежит.
var car = { make: '....' func: () => {console.log(this.make)} }
this в приведенном фрагменте относится к объекту car.
Оно принимает разные значения в зависимости от использования
- Внутри метода.
- Внутри функции.
- В одиночестве.
- В случае использования внутри события.
- call(), apply() и bind()
.
Внутри метода.
Когда this используется внутри метода, оно ссылается на объект владельца метода.
Функции, определенные внутри объекта, называются методами. Давайте снова рассмотрим пример с нашей машиной.
var car = { make: "Lamborghini", model: "Huracán", fullName: function () { console.log(this.make + " " + this.model); console.log(car.make + " " + car.model); } } car.fullName();
Здесь fullName() это метод. this внутри этого метода принадлежит car.
Внутри функции
Рассказать о this внутри функции немного сложнее. Первое, что нужно понять, это то, что, как и все объекты, оно имеет свойства, функции которое также имеют свойства. Всякий раз, когда эта функция выполняется, она получает свойство this, которое является переменной со значением объекта, которое ее вызывает.
на самом деле this в данном случае просто краткая ссылка на «предшествующий объект» — вызывающий текущий объект.
Если функция не вызывается объектом, то this внутри функции принадлежит глобальному объекту, которым является window. В этом случае оно будет относиться к значениям, определенным в глобальной области видимости. Давайте рассмотрим пример:
var make= "Mclaren"; var model= "720s" function fullName() { console.log(this.make + " " + this.model); } var car = { make:"Lamborghini", model:"Huracán", fullName:function () { console.log (this.make + " " + this.model); } } car.fullName(); // Lmborghini Huracán window.fullName(); // Mclaren 720S fullName(); // Mclaren 720S
Здесь make, model и fullName определены как глобально, так и внутри объекта car . При вызове объектом car this относится к свойствам, определенным внутри объекта. С другой стороны, два других вызова функций одинаковы и возвращают глобально определенные свойства.
В одиночестве
Когда this используется отдельно, а не внутри какой-либо функции или объекта, оно относится к глобальному объекту.
var make = "Mclaren"; var model = "720s"; var name = "Ferrari"; console.log(this.name); // Ferrari
Здесь this относится к глобальному свойству name.
В случае использования внутри события.
События могут быть любого типа, но для простоты и ясности давайте рассмотрим событие клика.
При каждом нажатии кнопки и создание события может вызываться обработчик события для выполнения определенной задачи, основанной на клике. Если this используется внутри этой функции, оно будет ссылаться на элемент, вызвавший событие. В DOM все элементы хранятся в виде объектов. Вот почему, когда событие вызывается, оно ссылается на этот элемент, потому что этот элемент веб-страницы на самом деле является объектом внутри DOM.
Пример:
<button onclick="this.style.display='none'"> Remove Me! </button>
call(), apply() и bind()
- bind: позволяет нам установить this значение на методы.
- call и apply: Позволяет нам заимствовать функции и устанавливать
this
значение при вызове функции.
Call, Bind и Apply сами по себе являются темой другого поста.
Самая хитрая часть
Если понять как работает this, то его использование облегчит вашу работу. Но есть некоторые случаи, когда могут возникнуть сложности с понимаем.
Example 1.
var car = { make: "Lamborghini", model: "Huracán", name: null, fullName: function () { this.name = this.make + " " + this.model; console.log(this.name); } } var anotherCar = { make: "Ferrari", model: "Italia", name: null } anotherCar.name = car.fullName(); // Lamborghini Huracán
Здесь мы получаем неожиданный результат. Мы заимствовали метод, который использует this из другого объекта, и проблема в том, что метод присваивается функции anotherCar, а фактически вызывается для объекта car. Вот почему мы получаем результат как Lamborghini, а не Ferrari.
Чтобы решить эту проблему, мы используем метод call().
var car = { make: "Lamborghini", model: "Huracán", name: null, fullName: function () { this.name = this.make + " " + this.model; console.log(this.name); } } var anotherCar = { make: "Ferrari", model: "Italia", name: null } car.fullName.call(anotherCar); console.log(car.name); // Ferrari Italia console.log(anotherCar.name); // Ferrari Italia
Здесь метод call() вызывает метод fullName() для объекта anotherCar, который изначально не имеет функции fullName().
Мы также можем видеть, что когда мы регистрируем car.name и anotherCar.name, мы получаем результат для последнего, а не для первого, что означает, что функция действительно была вызвана на anotherCar, а не на car.
Example 2.
var cars = [ { make: "Mclaren", model: "720s" }, { make: "Ferrari", model: "Italia" } ] var car = { cars:[{make:"Lamborghini", model:"Huracán"}], fullName:function () { console.log(this.cars[0].make + " " + this.cars[0].model); } } var vehicle = car.fullName; vehicle() // Mclaren 720s
В приведенном выше фрагменте у нас есть глобальный объект с именем cars, и у нас есть объект с таким же именем внутри объекта car. Затем метод fullName() присваивается переменной vehicle, которая затем вызывается. Переменная принадлежит глобальному объекту, поэтому this вызывает глобальный объект cars вместо объекта cars из-за принадлежности к контексту.
Чтобы решить эту проблему, используем функцию .bind().
... var vehicle = car.fullName.bind(car); vehicle() // Lamborghini Huracán
Bind помогает нам явно установить this и указать переменной vehicle принадлежность контекста к car, а не к глобальному объекту.
Пример 3.
var car = { cars: [ { make:"Lamborghini",model:"Huracán" }, { make: "Mclaren", model: "720s" }, { make: "Ferrari",model: "Italia" } ], model: 'lamborgini', fullName: function() { this.cars.forEach(function(){ console.log (this.make + " " + this.model); }) } } car.fullName(); // (3) undefined undefined
В приведенном выше фрагменте fullName() вызывает функцию, которая перебирает массив cars с помощью forEach. Внутри forEach есть анонимная функция, которая теряет контекст. Функция внутри функции в JavaScript называется замыканием.
Другая важная концепция, играющая роль здесь, — это scope(область действия). Переменная внутри функции не может получить доступ к переменным и свойствам вне ее области. this внутри функции не может получить доступ к this вне ее. Так что this некуда идти, кроме как искать в глобальный области видимости. Но там нет свойства определенного для this поэтому выводиться undefined.
Обходной путь для вышеупомянутого состоит в том, что мы можем присвоить переменной значение this вне анонимной функции и затем использовать ее внутри нее.
var car = { cars: [ { make:"Lamborghini",model: "Huracán" }, { make: "Mclaren", model: "720s" }, { make: "Ferrari",model: "Italia" } ], model: 'lamborgini', fullName: function() { var self = this; this.cars.forEach(function(car) { console.log (car.make + " " + self.model); }) } } car.fullName(); // Lamborghini lamborgini // Mclaren lamborgini // Ferrari lamborgini
Здесь переменная self содержит значение this, которое используется с внутренней функцией.
Пример 4.
var car= { make: "Lamborghini", model: "Huracán", fullName: function (cars) { cars.forEach(function(vehicle){ console.log(vehicle + " " + this.model); }) } } car.fullName(['lambo','ferrari','porsche']); // lambo undefined // ferrari undefined // porsche undefined
Это пересмотренный пример, в котором this снова оказалось недоступным. Давайте использовать функцию стрелки ( => ), чтобы решить эту проблему без использования self:
var car= { make: "Lamborghini", model: "Huracán", fullName: function (cars) { cars.forEach((vehicle) => { console.log(vehicle + " " + this.model); }) } } car.fullName(['lambo','ferrari','porsche']); // lambo Huracán // ferrari Huracán // porsche Huracán
Как видите, использование функции стрелки ( => ) в forEach() автоматически решает проблему, и нам не нужно выполнять привязку или передавать значение this какой-либо другой переменной. Это связано с тем, что функции-стрелки ( => ) привязывают свой контекст к исходному, так что на самом деле здесь this относится к исходному контексту.
Пример 5.
var car= { make: "Lamborghini", model: "Huracán", fullName: function () { console.log(this.make + " " + this.model); } } var truck= { make: "Tesla", model: "Truck", fullName: function (callback) { console.log(this.make + " " + this.model); callback(); } } truck.fullName(car.fullName); // Tesla Truck // undefined undefined
Приведенный выше код состоит из двух идентичных объектов, один из которых содержит функцию обратного вызова (callback). Функция обратного вызова — это функция, передаваемая в другую функцию в качестве аргумента, которая затем вызывается внутри внешней функции.
Здесь метод fullName объекта truck содержит обратного вызова (callback), который также вызывается внутри него. Когда мы вызываем метод fullName объекта truck с обратным вызовом (аргументом) в качестве метода fullName объекта car, мы получаем вывод в виде Tesla Truck и undefined.
Возможно некоторые из вас могли предположить, что car.fullName отобразит модель и марку объекта грузовика, но, это не так, this снова сыграло с нами злую шутку. Здесь car.fullName передается в качестве аргумента и фактически не вызывается объектом truck. Обратный вызов вызывает метод объекта car, но обратите внимание, что обратный вызов callback связывает его this с глобальным объектом.
Таким образом, чтобы получить результат, мы создадим глобальные переменные make и model.
make = "Porsche"; model = "Carerra"; var car= { make: "Lamborghini", model: "Huracán", fullName: function () { console.log(this.make + " " + this.model); } } var truck= { make: "Tesla", model: "Truck", fullName: function (callback) { console.log(this.make + " " + this.model); callback(); } } truck.fullName(car.fullName); // Tesla Truck // Porsche Carerra
Таким образом мы можем убедиться, что this ссылается на глобальный объект.
Чтобы получить желаемый результат, результат car.fullName, воспользуемся bind(), чтобы жестко привязать объект car к обратному вызову.
make = "Porsche"; model = "Carerra"; var car = { make: "Lamborghini", model: "Huracán", fullName: function () { console.log(this.make + " " + this.model); } } var truck = { make: "Tesla", model: "Truck", fullName: function (callback) { console.log(this.make + " " + this.model); callback(); } } truck.fullName(car.fullName.bind(car)); // Tesla Truck // Lamborghini Huracán
Заключение
Без сомнения, использование this очень полезно, но в этом есть и свои подводные камни. Надеюсь, я рассказал об этом максимально понятно.