Автор материала, перевод которого мы сегодня публикуем, говорит, что когда она работала в сфере бухучёта, там применялись понятные термины, значения которых легко найти в словаре. А вот занявшись программированием, и, в частности, JavaScript, она начала сталкиваться с такими понятиями, определения которых в словарях уже не найти. Например, это касается ключевого слова this. Она вспоминает то время, когда познакомилась с JS-объектами и функциями-конструкторами, в которых использовалось это ключевое слово, но добраться до его точного смысла оказалось не так уж и просто. Она полагает, что подобные проблемы встают и перед другими новичками, особенно перед теми, кто раньше программированием не занимался. Тем, кто хочет изучить JavaScript, в любом случае придётся разобраться с this. Этот материал направлен на то, чтобы всем желающим в этом помочь.



Что такое this?


Предлагаю вашему вниманию моё собственное определение ключевого слова this. This — это ключевое слово, используемое в JavaScript, которое имеет особое значение, зависящее от контекста в котором оно применяется.

Причина, по которой this вызывает столько путаницы у новичков, заключается в том, что контекст this меняется в зависимости от его использования.

This можно считать динамическим ключевым словом. Мне нравится, как понятие «контекст» раскрыто в этой статье Райана Морра. По его словам, контекст всегда является значением ключевого слова this, которое ссылается на объект, «владеющий» кодом, выполняемым в текущий момент. Однако, тот контекст, который имеет отношение к this, это не то же самое, что контекст выполнения.

Итак, когда мы пользуемся ключевым словом this, мы, на самом деле, обращаемся с его помощью к некоему объекту. Поговорим о том, что это за объект, рассмотрев несколько примеров.

Ситуации, когда this указывает на объект window


Если вы попытаетесь обратиться к ключевому слову this в глобальной области видимости, оно будет привязано к глобальному контексту, то есть — к объекту window в браузере.

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

Попробуйте выполнить этот код, например, в консоли браузера:

console.log(this);

// в консоль выводится объект Window
// Window { postMessage: ƒ, 
// blur: ƒ, 
// focus: ƒ, 
// close: ƒ, 
// frames: Window, …}

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

// Вызовем функцию
myFunction(); 

// функция выводит тот же объект Window! 
// Window { postMessage: ƒ, 
// blur: ƒ, 
// focus: ƒ, 
// close: ƒ, 
// frames: Window, …}

Использование this внутри объекта


Когда this используется внутри объекта, это ключевое слово ссылается на сам объект. Рассмотрим пример. Предположим, вы создали объект dog с методами и обратились в одном из его методов к this. Когда this используется внутри этого метода, это ключевое слово олицетворяет объект dog.

var dog = {
  name: 'Chester',
  breed: 'beagle',
  intro: function(){
    console.log(this);
  }
};

dog.intro();

// в консоль выводится представление объекта dog со всеми его свойствами и методами
// {name: "Chester", breed: "beagle", intro: ƒ}
//    breed:"beagle"
//    intro:ƒ ()
//    name:"Chester"
//    __proto__:Object

This и вложенные объекты


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

var obj1 = {
  hello: function() {
    console.log('Hello world');
    return this;
  },
  obj2: {
      breed: 'dog',
      speak: function(){
            console.log('woof!');
            return this;
        }
    }
};
 
console.log(obj1);
console.log(obj1.hello());  // выводит 'Hello world' и возвращает obj1
console.log(obj1.obj2);
console.log(obj1.obj2.speak());  // выводит 'woof!' и возвращает obj2

Особенности стрелочных функций


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

var objReg = {
  hello: function() {
    return this;
  }
};
 
var objArrow = {
    hello: () => this
};
 
objReg.hello(); // возвращает, как и ожидается, объект objReg 
objArrow.hello(); // возвращает объект Window!

Если, озадачившись рассматриваемым вопросом, заглянуть на MDN, там можно найти сведения о том, что стрелочные функции имеют более короткую форму записи, чем функциональные выражения и не привязаны к собственным сущностям this, arguments, super или new.target. Стрелочные функции лучше всего подходят для использования их в роли обычных функций, а не методов объектов, их нельзя использовать в роли конструкторов.

Прислушаемся к MDN и не будем использовать стрелочные функции в качестве методов объектов.

Использование this в обычных функциях


Когда обычная функция находится в глобальной области видимости, то ключевое слово this, использованное в ней, будет привязано к объекту window. Ниже приведён пример, в котором функцию test можно рассматривать в виде метода объекта window.

function test() {
  console.log('hello world');
  console.log(this);
}

test();

// hello world
// Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

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

function test() {
  'use strict';
  return this;
}

console.log( test() );
//функция возвращает undefined, а не объект Window

Обращение к this из функции, которая была объявлена за пределами объекта, а потом назначена в качестве его метода


Рассмотрим пример с уже известным нам объектом dog. В качестве метода этого объекта можно назначить функцию chase, объявленную за его пределами. Тут в объекте dog никаких методов не было, до тех пор, пока мы не создали метод foo, которому назначена функция chase. Если теперь вызвать метод dog.foo, то ��удет вызвана функция chase. При этом ключевое слово this, к которому обращаются в этой функции, указывает на объект dog. А функция chase, при попытке её вызова как самостоятельной функции, будет вести себя неправильно, так как при таком подходе this будет указывать на глобальный объект, в котором нет тех свойств, к которым мы, в этой функции, обращаемся через this.

var dog = {
  breed: 'Beagles',
  lovesToChase: 'rabbits'
};

function chase() {
  console.log(this.breed + ' loves chasing ' + this.lovesToChase + '.'); 
}

dog.foo = chase;
dog.foo(); // в консоль попадёт Beagles loves chasing rabbits.

chase(); //так эту функцию лучше не вызывать

Ключевое слово new и this


Ключевое слово this находит применение в функциях-конструкторах, используемых для создания объектов, так как оно позволяет, универсальным образом, работать со множеством объектов, создаваемых с помощью такой функции. В JavaScript есть и стандартные функции-конструкторы, с помощью которых, например, можно создавать объекты типа Number или String. Подобные функции, определяемые программистом самостоятельно, позволяют ему создавать объекты, состав свойств и методов которых задаётся им самим.

Как вы уже поняли, мне нравятся собаки, поэтому опишем функцию-конструктор для создания объектов типа Dog, содержащих некоторые свойства и методы.

function Dog(breed, name, friends){
    this.breed = breed;
    this.name = name;
    this.friends = friends;	
    this.intro = function() {
        console.log(`Hi, my name is ${this.name} and I’m a ${this.breed}`);
        return this;
    }; 
}

Когда функцию-конструктор вызывают с использованием ключевого слова new, this в ней указывает на новый объект, который, с помощью конструктора, снабжают свойствами и методами.

Вот как можно работать со стандартными конструкторами JavaScript.

var str = new String('Hello world');
/*******
Строки можно создавать так, но лучше этого не делать, используя подход, применённый при объявлении переменной str2 ниже. Одна из причин подобной рекомендации заключается в том, что в JavaScript строки удобно создавать, пользуясь строковыми литералами, когда строкой считается всё, включённое в двойные или одинарные кавычки. То же самое касается и других примитивных значений. Стоит отметить, что мне, на практике, не встречалась ситуация, когда надо было бы использовать конструкторы для создания значений примитивных типов.
*******/

var str2 = 'Hello world';
// когда строка объявлена так, система, всё равно, позволяет работать с ней как с объектом

Теперь поработаем с только что созданной функцией-конструктором Dog.

// Создадим новый экземпляр объекта типа Dog
var chester = new Dog('beagle', 'Chester', ['Gracie', 'Josey', 'Barkley']);
chester.intro();        // выводит Hi, my name is Chester and I'm a beagle
console.log(chester);   // выводит Dog {breed: "beagle", name: "Chester", friends: Array(3), intro: ƒ}

Вот ещё один пример использования функций-конструкторов.

var City = function(city, state) {
  this.city = city || "Phoenix";
  this.state = state || "AZ";
  this.sentence = function() {
    console.log(`I live in ${this.city}, ${this.state}.`);
  };
};

var phoenix = new City(); // используем параметры по умолчанию
console.log(phoenix); // выводит в консоль строковое представление объекта
phoenix.sentence(); // выводит I live in Phoenix, AZ.

var spokane = new City('Spokane', 'WA');
console.log(spokane); // выводит сам объект
spokane.sentence(); // выводит I live in Spokane, WA.

О важности ключевого слова new


При вызове функции-конструктора с использованием ключевого слова new ключевое слово this указывает на новый объект, который, после некоторой работы над ним, будет возвращён из этой функции. Ключевое слово this в данной ситуации весьма важно. Почему? Всё дело в том, что с его помощью можно, используя единственную функцию-конструктор, создавать множество однотипных объектов.

Это позволяет нам масштабировать приложение и сокращать дублирование кода. Для того чтобы понять важность этого механизма, подумайте о том, как устроены учётные записи в социальных сетях. Каждая учётная запись может представлять собой экземпляр объекта, создаваемый с помощью функции-конструктора Friend. Каждый такой объект можно заполнять уникальными данными о пользователе. Рассмотрим следующий код.

// Функция-конструктор
var Friend = function(name, password, interests, job){
  this.fullName = name;
  this.password = password;
  this.interests = interests;
  this.job = job;
};

function sayHello(){
   // раскомментируйте следующую строчку, чтобы узнать, на что указывает this
   // console.log(this); 
  return `Hi, my name is ${this.fullName} and I'm a ${this.job}. Let's be friends!`;
}

// Мы можем создать один или несколько экземпляров объекта типа Friend, используя ключевое слово new
var john = new Friend('John Smith', 'badpassword', ['hiking', 'biking', 'skiing'], 'teacher'); 

console.log(john); 

// Назначим функцию ключу greeting объекта john
john.greeting = sayHello; 

// Вызовем новый метод объекта
console.log( john.greeting() ); 

// Помните о том, что sayHello() не стоит вызывать как обычную функцию
console.log( sayHello() ) ;

Итоги


На самом деле, особенности использования ключевого слова this в JavaScript не ограничиваются вышеописанными примерами. Так, в череду этих примеров можно было бы включить использование функций call, apply и bind. Так как материал этот рассчитан на начинающих и ориентирован на разъяснение основ, мы их здесь не касаемся. Однако если сейчас у вас сформировалось начальное понимание this, то и с этими методами вы вполне сможете разобраться. Главное — помните о том, что если что-то с первого раза понять не удаётся, не прекращайте учиться, практикуйтесь, читайте материалы по интересующей вас теме. В одном из них вам обязательно попадётся нечто такое (какая-то удачная фраза, например), что поможет понять то, что раньше понять не удавалось.

Уважаемые читатели! Возникали ли у вас сложности с пониманием ключевого слова this в JavaScript?