[Перевод] Ключевое слово this в JavaScript для начинающих

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

q7xdzywyxtydoitwvsnkg18opaq.png

Что такое 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?

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru