Ключевое слово this — это палка о двух концах: оно может быть источником сложных ошибок, а также может облегчить вам жизнь как разработчику, если вы узнаете, как оно работает на самом деле. В наши дни я чувствую, что сообщество подталкивает язык к более функциональным парадигмам. Мы не так часто используем ключевое слово this. Бьюсь об заклад, это все еще сбивает людей с толку, потому что его значение зависит от контекста. Итак, в этой статье будет объяснено ключевое слово this, чтобы дать вам хорошее представление о том, как оно работает на самом деле.

ВВЕДЕНИЕ

Эта статья предназначена для каждого разработчика Javascript. Вы узнаете следующее:

  • Что такое ключевое слово this в Javascript?
  • Что представляет ключевое слово this в Node.
  • Как определяется ключевое слово this в глобальном контексте и контексте выполнения функций.
  • Различные способы вызова функции и их связь с this
  • Как контролировать значение this с помощью методов call() и apply().
  • Как использовать метод bind().
  • Как this ведет себя в стрелочной функции

ЧТО ТАКОЕ this КЛЮЧЕВОЕ СЛОВО

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

Например: «Мария бежит быстро, потому что она пытается успеть на автобус».

В приведенном выше утверждении местоимение «она» используется для обозначения антецедента (антецедент — это существительное, к которому относится местоимение) «Мэри». Давайте свяжем эту концепцию с ключевым словом this.

const person = { name: "Mary",
                 pronoun: "she", 
Activity: function () { // this = person      console.log(`${person.name} is running fast because ${this.pronoun} is trying to catch the bus`) } }
person.Activity() // Mary is running fast because she is trying to catch the bus

В предыдущем коде this используется как ссылочное значение объекта person точно так же, как местоимение «она» используется для обозначения «Мэри».

КАК ОПРЕДЕЛЯЕТСЯ ЗНАЧЕНИЕ this

Ключевое слово this определяется контекстом выполнения, в котором оно используется. В двух словах, контекст выполнения — это абстрактная среда, используемая Javascript для выполнения кода, она бывает трех вариантов:

  1. Глобальный контекст выполнения
  2. Контекст выполнения функции

Существует также контекст выполнения eval(), но он редко используется из-за своей вредоносной природы. Прочтите эту статью, чтобы узнать больше о контексте выполнения.

Глобальный контекст выполнения

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

console.log(window === this ) // true this.color = 'Green' console.log(window.color) // Green

В предыдущем коде свойство добавляется к глобальному объекту window с помощью ключевого слова this.

N/B: в глобальном контексте выполнения ключевое слово this всегда будет ссылаться на глобальный объект, независимо от того, находится ли Javascript в строгом или нестрогом режиме.

this ключевое слово в узле

Согласно Документации NodeJS

В браузерах областью верхнего уровня является глобальная область. Это означает, что в браузерах, если вы находитесь в глобальной области видимости, var something будет определять глобальную переменную. В Node.js все иначе. Область верхнего уровня не является глобальной; var что-то внутри модуля Node.js будет локальным для этого модуля.

Приведенное выше утверждение означает, что ключевое слово this не ссылается на глобальный объект в NodeJS. Вместо этого он указывает на текущий модуль, в котором он использовался, то есть на объект, экспортированный через module.exports.

Например, давайте посмотрим на гипотетический модуль с именем app.js.

┣ 📄 app.js 
console.log(this); 
module.exports.color = 'Green'; 
console.log(this);

выход:

┣ $ node app.js 
{} 
{color: 'Green'}

В приведенном выше коде сначала регистрируется пустой объект, поскольку в module.exports модуля app.js нет значений. Затем свойство color добавляется к объекту module.exports, когда this снова регистрируется, обновленный объект module.exports возвращается со свойством color.

Как получить доступ к глобальному объекту в узле

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

┣ 📄 app.js console.log(global);

выход:

┣ $ node app.js // logs the node global object

Глобальный объект предоставляет множество полезных свойств среды Node.

Контекст выполнения функции

В контексте выполнения функции то, как определяется ключевое слово this, зависит от того, как функция вызывается или вызывается.

Функцию Javascript можно вызвать четырьмя способами:

  • Вызов как функция
  • Вызов как метод
  • Вызов как конструктор
  • Вызов с помощью методов apply и call

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

Пример

function A() { console.log(this === window) // true }
function B() { "use strict" console.log(this === window) // false } function C() { "use strict" console.log(this === undefined) // true}
 
A(); // true 
B(); // false 
C(); // true

Когда функция вызывается как метод (т. е. через свойство объекта), this ссылается на объект, «владеющий» методом.

Пример

let Nigeria = { continent: 'Africa', getContinent: function () { return this.continent; } } 
console.log(Nigeria.getContinent()); // Africa

Чтобы вызвать функцию как конструктор, мы перед вызовом функции используем оператор new.

Пример

function Context() {return this; } 
new Context();

Когда функция вызывается как конструктор (через оператор new), происходит несколько специальных действий:

  1. Создается новый пустой объект
  2. Этот объект передается конструктору как ссылочный объект this, т. е. объект this будет указывать на него при вызове функции.
  3. Вновь созданный объект возвращается как значение оператора new.

Пример

function Person() { this.name = 'Mary', this.age = 20 }
const person1 = new Person(); 
console.log(person1.age) // 20

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

Если вы попытаетесь вызвать функцию-конструктор без оператора new, this будет указывать на undefined, а не на объект.

Пример

function Person() { this.name = 'Mary', this.age = 20 } const person2 = Person(); console.log(person2.age) // // => TypeError: Cannot read property 'age' of undefined

Чтобы убедиться, что функция Person() всегда вызывается с помощью вызова конструктора, вы добавляете проверку в начале функции Person():

function Person() { if (!(this instanceof Person)) { throw Error('Must use the new operator to call the function'); } 
this.name = 'Mary',
this.age = 20 } 
const person2 = Person(); console.log(person2.age) // // => Must use the new operator to call the function

В ES6 появилось мета-свойство с именем new.target, которое позволяет определить, вызывается ли функция как простой вызов или как конструктор.

Вы можете изменить функцию Person(), чтобы использовать метасвойство new.target:

function Person() { 
if (!new.target) { throw Error('Must use the new operator to call the function'); }
 this.name = 'Mary', this.age = 20 }
 const person2 = Person(); 
console.log(person2.age)
 // // => Must use the new operator to call the function

Функции — это объекты, и, как и все объекты Javascript, они имеют методы. Два из этих методов, call() и apply(), косвенно вызывают функцию. Оба метода позволяют вам явно указать значение this (ссылка на объект) для вызова, что означает, что вы можете вызывать любую функцию как метод любого объекта, даже если она буквально не является методом этого объекта. call() и apply() также позволяют указать аргументы для вызова. Метод call() использует свой собственный список аргументов в качестве аргументов функции, а метод apply() ожидает, что в качестве аргументов будет использоваться массив значений. И в call(), и в apply() первым аргументом является ключевое слово this, которое представляет объект, для которого должна быть вызвана функция.

Пример

function getContinent(prefix) { console.log(`${prefix} ${this.continent}`); } 
let nigeria = { continent: 'Africa' };
let china = { continent: 'Asia' }; 
getContinent.call(nigeria, "Nigeria is in"); getContinent.call(china, "China is in");

Выход:

Nigeria is in Africa China is in Asia

В этом примере мы вызвали функцию getContinent() косвенно, используя метод call() функции getContinent(). Мы передали объекты nigeria и china в качестве первого аргумента метода call(), поэтому при каждом вызове мы получали соответствующий континент страны.

Метод apply() похож на метод call(), но, как вы уже знаете, его второй аргумент представляет собой массив аргументов.

getContinent.apply(nigeria, ["Nigeria is in"]); getContinent.apply(china, ["China is in"]);

Выход:

Nigeria is in Africa China is in Asia

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

Давайте рассмотрим несколько примеров:

let getThis = () => this; console.log(getThis() === window); // true

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

  1. Стрелочная функция getThis лексически ограничена функцией Global() 'не стрелочной', которая возвращает глобальный объект window.
  2. Значение this в стрелочной функции getThis является глобальным объектом window, поскольку значение this функции лексически ограничено, чтобы указывать на этот объект.

Давайте посмотрим на другой пример:

function confirmThis () { let getThis = () => this; console.log(getThis() === window); // true } confirmThis();

Выход:

Поскольку значение this «нормальной» функции указывает на глобальный объект window в «нестрогом режиме». Значение this также будет указывать на объект window, так как оно лексически связано с функцией confirmThis(). Однако в «строгом» режиме дело обстоит иначе.

function confirmThis () { "use strict" let getThis = () => this; console.log(getThis() === window); // true } confirmThis();

Выход:

Значение this функции confirmThis() будет undefined в строгом режиме, то же самое относится к стрелочной функции getThis.

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

Пример

const module = { x: 42, getX: function() { return this.x; } };
 const unboundGetX = module.getX; console.log(unboundGetX());
 // The function gets invoked at the global scope 
// expected output: undefined
const boundGetX = unboundGetX.bind(module); console.log(boundGetX()); // expected output: 42

В предыдущем коде объектный метод module getX() вызывается как "функция" (вместо метода module) в глобальной области видимости. Это приводит к потере ссылки this на объект module. Чтобы this по-прежнему указывал на объект module, когда метод getX вызывается как "функция" вместо "метода", он должен быть "привязан" к объекту module через метод bind()-const boundGetX = unboundGetX.bind(module);.

ЗАКЛЮЧЕНИЕ

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

Краткое содержание

В этой статье вы узнали следующее:

  • Что такое ключевое слово this в Javascript?
  • Что представляет ключевое слово this в Node.
  • Как определяется ключевое слово this в глобальном контексте и контексте выполнения функций.
  • Различные способы вызова функции и их связь с this
  • Как контролировать значение this с помощью методов call() и apply().
  • Как пользоваться методом bind().
  • Как this ведет себя в стрелочной функции

ГЛОССАРИЙ

Стек или стек вызовов: стек — это структура данных, которая следует принципу последним пришел — первым вышел (LIFO). Однако стек выполнения — это стек, в котором отслеживается весь контекст выполнения, созданный во время выполнения кода. В стеке также хранятся статические данные в Javascript (переменные и ссылочные значения). Узнать больше здесь

Куча: куча — это структура данных, используемая для хранения динамических данных в Javascript. Здесь хранятся все объекты Javascript. Подробнее здесь.

Лексическая область: прочитайте этот ответ на переполнение стека для лучшего понимания.

Строгий режим Javascript: MDN

СОВЕТ ОТ РЕДАКТОРА: Чтобы узнать больше о внутренних деталях JavaScript, не пропустите другие статьи того же автора: Объяснение типов и значений JavaScript и Объяснение контекста выполнения JavaScript и стека.