Ключевое слово this
1. Контекст исполнения (this)
Можно с уверенностью сказать, что ключевое слово this
является одной из самых запутанных частей JavaScript на старте изучения. Новички часто подставляют this
методом научного тыка до тех пор, пока скрипт не сработает 😐.
Контекст в JavaScript похож на контекст в предложении:
Петя
бежит быстро, потому чтоПетя
пытается поймать поезд.Петя
бежит быстро, потому чтоон
пытается поймать поезд.
Второе предложение звучит лаконичнее. Предметом предложения является Петя и мы можем сказать, что контекст предложения — это Петя, потому что он в центре внимания в это конкретное время в предложении. Даже местоимение кто относится к Пете.
И точно так же объект может быть текущим контекстом исполнения функции.
// Петя бежит быстро, потому что Петя пытается поймать поезд.
const petya = {
name: 'Petya',
showName() {
console.log(petya.name);
},
};
petya.showName();
Copy
Обращение к свойствам объекта внутри методов, используя имя самого объекта, аналогично использованию Петя
вместо он
.
У всех функций есть локальная переменная this
. Во время исполнения функции в переменную this
записывается ссылка на объект в контексте которого она вызывается.
this
нам нужен для доступа к методам и свойствам объекта, который вызывает функцию, тем более, что чаще всего имя вызывающего объекта не известно.
// Петя бежит быстро, потому что он (this) пытается поймать поезд.
const petya = {
name: 'Petya',
showName() {
console.log(this.name);
},
};
petya.showName();
Copy
2. Правила определения this
Необходимо усвоить всего одно правило для определения this
.
Значение контекста внутри функции определятся не в момент ее создания, а в момент вызова. То есть значение this
определяется тем, как вызывается функция, а не где она была объявлена.
2.1. this в глобальной области видимости
В глобальной области видимости, если скрипт выполняется не в строгом режиме, this
ссылается на объект window
. В строгом режиме значение this
, в глобальной области видимости, будет undefined
.
function fn() {
console.log(this);
}
fn(); // window без "use strict" и undefined с "use strict"
Copy
2.2. this в методе объекта
Если функция была вызвана как метод объекта, то контекст будет ссылаться на объект, частью которого является метод.
const petya = {
name: 'Petya',
showThis() {
console.log(this);
},
showName() {
console.log(this.name);
},
};
petya.showThis(); // {name: "Petya", showThis: ƒ, showName: ƒ}
petya.showName(); // 'Petya'
Copy
Более сложный пример для лучшего понимания:
Сначала создадим функцию в глобальной области видимости и вызовем ее.
После чего присвоим ее в свойство объекта и вызовем как метод этого объекта.
function showThis() {
console.log('this in showThis: ', this);
}
// Вызываем в глобальном контексте
showThis();
// this in showThis: Window
const user = { name: 'Mango' };
/*
* Записываем ссылку на функцию в свойство объекта
* Обратите внимание, что это не вызов - нет ()
*/
user.showContext = showThis;
/*
* Вызываем функцию в контексте объекта
* this будет указывать на текущий объект, в контексте
* которого осуществляется вызов, а не на глобальный объект.
*/
user.showContext();
// this in showThis: {name: "Mango", showContext: ƒ}
Copy
2.3. this в функциях обратного вызова
Когда мы передаем метод, использующий this в качестве параметра, который будет использоваться как функция обратного вызова, будет проблема. Решение этой проблемы рассматривается в следующей секции.
const hotel = {
name: 'Resort Hotel',
showThis() {
console.log(this);
},
};
const fn = function (callback) {
/*
* Во время вызова fn, callback будет ссылкой
* на функцию showThis объекта hotel.
* Ее вызов происходит в глобальном контексте,
* про hotel она ничего не знает.
* Соответственно this не будет ссылаться на hotel
*/
callback();
};
// Передается ссылка на функцию а нее ее вызов
fn(hotel.showThis); // window или undefined
Copy
2.4. this в стрелочных функциях
Стрелочные функции не имеют своего this
. В отличии от обычных функций, изменить значение this
внутри стрелки после ее объявления нельзя.
Контекст внутри стрелки определяется местом ее объявления, а не вызова и ссылается на контекст родительской функции.
Стрелочные функции также игнорируют наличие строгого режима. Если стрелка запомнила глобальный контекст, то this
в ней будет содержать ссылку на window
вне зависимости выполняется ли скрипт в строгом режиме или нет.
const showThis = () => {
console.log('this in showThis: ', this);
};
showThis(); // this in showThis: window
const user = { name: 'Mango' };
user.showContext = showThis;
user.showContext(); // this in showThis: window
Copy
Ограничивая стрелочные функции постоянным контекстом, JavaScript-движки могут лучше их оптимизировать, в отличие от обычных функций, значение this
которых может быть изменено.
Пример не практичный, но отлично показывает как работает контекст для стрелок. Значение контекста берется из родительской области видимости.
const hotel = {
name: 'Resort hotel',
showThis() {
const fn = () => {
/*
* Стрелки запоминают контекст во время объявления,
* из родительской области видимости
*/
console.log('this in fn: ', this);
};
fn();
console.log('this in showThis: ', this);
},
};
hotel.showThis();
// this in fn: {name: 'Resort hotel', showThis: ƒ}
// this in showThis: {name: 'Resort hotel',showThis: ƒ}
Copy
Если привести этот код к ES5, получится следующее.
const hotel = {
name: 'Resort hotel',
showThis: function showThis() {
/*
* Контекст для стрелки сохраняется
* и передается из внешней области видимости
*/
const context = this;
const fn = function fn() {
// А тут используется
console.log('this in fn: ', context);
};
fn();
console.log('this in showThis: ', this);
},
};
hotel.showThis();
// this in fn: {name: 'Resort hotel', showThis: ƒ}
// this in showThis: {name: 'Resort hotel',showThis: ƒ}
Copy
3. Методы функций call, apply, bind
Присвоение функции в качестве метода объекта может показаться хорошей идеей. Но стоит ли хранить подобные методы? Дублирование уже существующих функций в виде методов объекта будет занимать ресурсы, не принося никаких заметных выгод.
Теперь представьте что у вас 150 отелей и для каждого необходимо выполнить ту же самую операцию при каждом приветствии гостя. Нам, как минимум, хочется сразу вынести этот общий код в функцию.
Функция - это на самом деле довольно хитрый объект, поэтому у нее тоже есть методы. С помощью методов call
и apply
можно выполнить функцию в контексте какого-то объекта, не делая функцию его методом.
const greet = function () {
return `Wellcome to ${this.name} hotel!`;
};
const hotel = { name: 'Resort Hotel' };
console.log(greet.call(hotel)); // "Wellcome to Resort Hotel!"
console.log(greet.apply(hotel)); // "Wellcome to Resort Hotel!"
Copy
3.1. call и аргументы
fn.call(obj, arg1, arg2, ...)
Copy
Запомнить правило использования call
довольно легко: метод call
вызовет функцию fn
передав ее this
ссылку на объект obj
, а также аргументы arg1
, arg2
и т. д.
const greet = function (guest, stars) {
return `${guest}, welcome to ${stars}-star ${this.name}!`;
};
const hotel = { name: 'Resort Hotel' };
const motel = { name: 'Sunlight Motel' };
console.log(greet.call(hotel, 'Mango', 5));
// "Mango, welcome to 5-star Resort Hotel!"
console.log(greet.call(motel, 'Poly', 4));
// "Poly, welcome to 4-star Sunlight Motel!"
Copy
3.2. apply и аргументы
fn.apply(obj, [arg1, arg2, ...])
Copy
Метод apply
- полный аналог метода call
за исключением того, что синтаксис вызова аргументов требует не перечисление, а массив.
const greet = function (guest, stars) {
return `${guest}, welcome to ${stars}-star ${this.name}!`;
};
const hotel = { name: 'Resort Hotel' };
const motel = { name: 'Sunlight Motel' };
console.log(greet.apply(hotel, ['Mango', 5]));
// "Mango, welcome to 5-star Resort Hotel!"
console.log(greet.apply(motel, ['Poly', 4]));
// "Poly, welcome to 4-star Sunlight Motel!"
Copy
3.3. bind
Мы рассмотрели случаи, когда необходимо мгновенно вызвать функцию с другим контекстом - для этого используются методы call
и apply
. Но в случае функции обратного вызова, когда необходимо не вызвать функцию на месте, а передать ссылку на эту функцию, причем с привязанным контекстом, call
и apply
не подходят. Метод bind
позволяет решить эту задачу.
const boundFn = fn.bind(obj, arg1, arg2, ...)
Copy
Метод bind
создает копию функции fn
с привязанным контекстом obj
и аргументами arg1
, arg2
и так дале, после чего возвращает ее как результат своей работы. В результате мы получаем копию функции с привязанным контекстом, которую можно передать куда угодно и вызвать когда угодно.
const greet = function (guest) {
return `${guest}, welcome to ${this.name}!`;
};
const hotel = { name: 'Resort Hotel' };
const hotelGreeter = greet.bind(hotel, 'Mango');
hotelGreeter(); // "Mango, welcome to Resort Hotel!"
Copy
Чаще всего метод bind
используется для привязки контекста при передаче методов объекта как функций обратного вызова. Возьмем проблемный пример из предыдущей секции. Задачу привязки контекста мы теперь можем решить используя метод bind
, передав функцией обратного вызова копию метода с привязанным контекстом.
const hotel = {
name: 'Resort Hotel',
showThis() {
console.log(this);
},
};
const fn = function (callback) {
callback();
};
// Передаем копию метода showThis с контекстом привязанным к hotel
fn(hotel.showThis.bind(hotel)); // {name: "Resort Hotel", showThis: ƒ}
Last updated