Ключевое слово this в JavaScript
JavaScript

Ключевое слово this в JavaScript

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

Неаккуратное использование this в JavaScript может стать источником серьезных и трудно отлавливаемых ошибок, что нередко приводит в замешательство начинающих JavaScript-программистов.

Эта статья не для гуру JS’а, вы не найдете здесь ничего нового. Если же вы только знакомитесь с этим замечательным языком — добро пожаловать в кроличью нору ;) В статье мы попытаемся немного пролить свет на особенности использования ключевого слова this, а также рассмотрим несколько щекотливых ситуаций, когда использование this вызывает наибольшие трудности.

Основы использования ключевого слова this

Для начала рассмотрим пример использования this в JavaScript:


var user = {
    name       : 'John',
    lastName   : 'Smith',
    getFullName: function() {
        console.log(this.name + ' ' + this.lastName);
        console.log(user.name + ' ' + user.lastName);
    }
}

В примере выше, функция getFullName дважды выводит один и тот же результат. В первом случае мы выводим свойства объекта, в контексте которого выполняется функция. Во втором случае — явно указываем, что хотим получить свойства объекта user.

Преимущество this очевидно: если мы в будущем захотим, скажем, переименовать объект user, при использовании второго варианта нам придется изменять его имя во всех подобных методах. Таким образом, использование this делает наш код более эстетичным и чистым: подобно местоимению, ключевое слово this можно использовать как указатель на тот объект, в контексте которого выполняется функция.

Что же такое this? Переменная this в функции всегда ссылается на объект, который вызывает эту функцию как метод. Если же функция вызывается просто как функция, а не как метод какого-либо объекта — значение this ссылается на глобальный объект (в строгом режиме в этом случае значением this будет undefined).

Таким образом, переменная this, используемая внутри функции (скажем, функции A), содержит ссылку на объект, который вызывает функцию А. Т.е. переменная this позволяет получить доступ к свойствам и методам объекта, вызывающего функцию A. Это особенно актуально в том случае, если мы не всегда знаем, какой объект вызывает функцию. Таким образом, this — это своего рода ярлык внутри функции, ссылка на вызывающий функцию объект.

Простейший пример использования this в JavaScript:


var user = {
    name       : 'John',
    lastName   : 'Smith',
    getFullName: function() {
        console.log(this.name + ' ' + this.lastName);
    }
}
user.getFullName();

Самое главное, что нужно помнить при использовании ключевого слова this: this не присваивается никакого значения до тех пор, пока функция не вызвана.

Использование this в глобальной области видимости

В глобальном контексте, если код выполняется в браузере, все переменные и функции добавляются к глобальному объекту window. Поэтому при вызове функции в глобальном контексте значением переменной this будет ссылка на глобальный объект window (кроме режима strict mode, об этом упоминалось ранее).

Рассмотрим этот момент на примере:


var name = 'John Smith';
function showName() {
    console.log(this.name);
}

var user = {
    name: 'Ellen Simons',  
    showName: function() {  
        console.log(this.name);  
    }
}

showName();        // John Smith
window.showName(); // John Smith
user.showName();   // Ellen Simons

В данном примере, вызывая глобальную функцию showName(), мы обращаемся к объекту window и пытаемся получить значение переменной name.

Обратите внимание, если в начале примера написать строчку «use strict»; — то при вызове глобальной функции showName() возникнет ошибка, т.к. значением переменной this будет undefined.

Когда использовать this следует наиболее осторожно

Использование this становится не совсем очевидным:

  • при использовании метода в качестве функции обратного вызова;
  • при использовании this внутри замыкания — во внутренней функции;
  • при присваивании метода;
  • при заимствовании метода.

Проблема во всех этих примерах заключается в том, что контекст выполнения функции — это объект, который вызывает функцию, и мы можем изменить контекст, вызвав эту функцию с другим объектом. Рассмотрим подробнее каждый из этих сценариев, а также способы решения проблемы.

Использование метода в качестве функции обратного вызова

Один из тонких моментов при использовании ключевого слова this — передача метода, использующего this в качестве callback’а другой функции.

Рассмотрим пример:


var users = {
  data: [
      {name: 'John Smith'},
      {name: 'Ellen Simons'}
  ],

   clickHandler: function(event) {
       console.log(this.data[0].name);
   }
}
 
$('button').click(users.clickHandler); // users.data is undefined

В этом примере у нас есть объект users c методом clickHandler, использующим this. При использовании этого метода, мы предполагаем, что объектом this в данном случае будет выступать объект users. Но когда мы передаем этот метод в качестве функции обратного вызова — значением объекта this будет объект кнопки. Этот момент не вполне очевиден, однако вызывает неприятные ошибки.

Если мы хотим, чтобы this.data действительно указывал на свойство data объекта users — мы должны использовать один из методов bind(), apply() или call() как раз для того, чтобы явно задать значение this. Т.е. вместо:

$('button').click(user.clickHandler);

Необходимо использовать:

$('button').click(user.clickHandler.bind(user));

Использование this внутри замыкания

Другой случай, когда могут возникать проблемы с использованием this — когда мы используем внутреннюю функцию (замыкание). Здесь важно иметь в виду, что замыкание не может получить доступ к значению this внешней функции (в отличии от других переменных внешней функции), т.к. имеет собственное значение this.

Пример:


var users = {
  helloText: 'Hello',
  data: [
       {name: 'John Smith'},
       {name: 'Ellen Simons'}
   ],
   clickHandler: function() {   
       this.data.forEach(function (user) {
           console.log(this.helloText + ', ' + user.name);
       })
   }
}
users.clickHandler();

В данном примере мы опять-таки ожидаем, что значением this будет объект users — однако внутри замыкания свое значение this, в данном примере — глобальный объект window. Значением this.helloText будет undefined.

Как избежать ошибки при использовании this в замыканиях? Очень просто — проинициализировать значением this другую переменную во внешней функции и использовать ее в замыкании:


var users = {
    helloText: 'Hello',
    data: [
        {name: 'John Smith'},
        {name: 'Ellen Simons'}
    ],
    clickHandler: function() {
        var helloText = this.helloText;
  
        this.data.forEach(function (user) {
            console.log(helloText + ', ' + user.name);
        })
    }
}
users.clickHandler();

Присваивание метода переменной

Когда мы присваиваем метод переменной — this снова ведет себя не совсем так, как мы ожидаем.

Рассмотрим пример:


// Переменная в глобальной области видимости
var data = [
    {name: 'John Smith'},
    {name: 'Ellen Simons'}
];

var users = {
   data: [
       {name: 'Michael Fox'},
       {name: 'Emmie Adams'}
   ],
   showFirst: function(event) {
       console.log(this.data[0].name);
   }
}

var showFirstUser = users.showFirst;
showFirstUser(); // John Smith - из глобальной области видимости

В данном случае, когда мы вызываем функцию showFirstUser() значением this будет глобальный объект window, соответственно, значение data будет взято из глобальной области видимости.

Решением данной проблемы может быть использование метода bind() для явного указания значения this:


var showFirstUser = users.showFirst.bind(users);
showFirstUser(); // Michael Fox

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

Заимствование методов — это довольно распространенная, экономящая время практика среди JavaScript-разработчиков. Давайте рассмотрим на примере проблемы, которые могут возникать c this при заимствовании метода:


var customer = {
    name: 'Ellen',
    birthDay: '1988-02-29',
    age: null
}

var manager = {
    name: 'John',
    birthDay: '1984-01-11',
    age: null,
    getAge: function() {
        var birthDate = new Date(this.birthDay);
      
        this.age = (new Date).getFullYear() - birthDate.getFullYear();
    }
}

customer.age = manager.getAge();

console.log(customer); // age = undefined
console.log(manager);  // age = 30

В данном примере функция manager.getAge() вызывается в контексте объекта manager, соответственно значение свойства age будет установлено у объекта manager.

Чтобы избежать подобных проблем, можно воспользоваться методом apply():


manager.getAge.apply(customer);

console.log(customer); // age = 26
console.log(manager);  // age = null

Заключение

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

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

Рассылка
Подпишитесь на рассылку и получайте дайджест новостей и статей.
Никакого спама!
Подписаться