[Перевод] Ключевое слово this в JavaScript для начинающих
Автор материала, перевод которого мы сегодня публикуем, говорит, что когда она работала в сфере бухучёта, там применялись понятные термины, значения которых легко найти в словаре. А вот занявшись программированием, и, в частности, 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?