Значение this — одна из самых запутанных составляющих JavaSсript. this может принимать различные значения в зависимости от того, где располагается.

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

Верхний уровень

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

К примеру, если у нас есть console.log(this) на верхнем уровне скрипта браузера, то мы должны увидеть объект window в качестве значения this. Можем подтвердить это, использовав:

console.log(this === window)

Внутри функций верхнего уровня

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

this принимает значение window, если скрипт не находится в строгом режиме. Однако, если это не так, то значение будет undefined.

Например, если у нас имеется:

'use strict';
function foo() {
console.log(this);
}
foo();

То мы увидим, что this принимает значение undefined.

И напротив, в случае:

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

Видим, что значение this — это window из console.log.

Внутри объектов

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

function fullName() {
  return `${this.firstName} ${this.lastName}`;
}const jane = {
  firstName: 'Jane',
  lastName: 'Smith',
  fullName
};
const alex = {
  firstName: 'Alex',
  lastName: 'Smith',
  fullName
};console.log(jane.fullName());
console.log(alex.fullName());

В коде, который показан сверху, у нас присутствует обычная функция fullName, которая ссылается на this.firstName и this.lastName и возвращает их комбинацию.

Тогда, если мы определим объекты jane и alex, поместим внутрь каждого функцию fullName и вызовем их, как это было сделано выше, мы получим:

Jane Smith

из первого console.log и:

Alex Smith

из второго console.log.

Внутри конструкторов

В JavaScript конструкторы — это функции, которые используют оператор new для создания нового экземпляра и изменения свойств this.

Например, если мы запустим следующий код без использования строгого режима:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}Person('Joe', 'Smith');
const {
  firstName,
  lastName
} = window;
console.log(firstName, lastName);

Тогда в качестве вывода console.log мы увидим Joe Smith, поскольку this — это window, если наш скрипт не использует строгий режим и относится к верхнему уровню.

Таким образом, this — это window, а следовательно firstName и lastName добавляются к window в качестве свойств. 

Это одна из причин того, зачем нужен строгий режим. Мы не хотим случайно добавлять новые глобальные переменные путем добавления свойств к window.

Если мы запустим код, приведенный выше, со строгим режимом, как здесь:

'use strict';
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}Person('Joe', 'Smith');
const {
  firstName,
  lastName
} = window;
console.log(firstName, lastName);

… тогда мы получим ошибку “Cannot set property ‘firstName’ of undefined” (не удается установить свойство ‘firstName’ для undefined), поскольку this в строгом режиме будет undefined.

Нам нужно вот что: использовать оператор new для вызова функций конструктора. Например, чтобы создать новый экземпляр Person, понадобится сделать следующее:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}const person = new Person('Joe', 'Smith');

Тогда мы получим:

{
  "firstName": "Joe",
  "lastName": "Smith"
}

… в качестве значения Person.

Если мы вызываем функцию конструктора с помощью оператора new, то this ссылается на текущий экземпляр функции конструктора.

Внутри классов

Поскольку классы JavaScript — это просто синтаксический сахар для функций конструктора, то всё, показанное выше для функций конструктора, применимо и к классам.

Например, этот код даст нам тот же результат, что и в предыдущем примере:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}const person = new Person('Joe', 'Smith');

Person должно оказаться:

{
  "firstName": "Joe",
  "lastName": "Smith"
}

Параметры constructor будут такими же, как и параметры в функциях конструктора.

Однако, если мы попытаемся вызвать класс Person без оператора new, то получим ошибку.

Это замечательная особенность использования классов вместо функций конструктора.

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

Стрелочные функции не привязаны к this, так что описанное выше к ним не относится.

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

const foo = () => {
  console.log(this);
}
foo();

В следующем примере мы получим undefined undefined для каждого console.log:

const fullName = () => {
  return `${this.firstName} ${this.lastName}`;
}const jane = {
  firstName: 'Jane',
  lastName: 'Smith',
  fullName
};
const alex = {
  firstName: 'Alex',
  lastName: 'Smith',
  fullName
};
console.log(jane.fullName());
console.log(alex.fullName());

Как видим, стрелочные функции не устанавливают значения this.firstName и this.lastName. this — это просто window, а не хост-объект, так что и то, и другое будет undefined.

Заключение

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

Случайно вызвать функции конструктора, как обычную функцию, легко. Если это произойдет без строгого режим, то мы ненамеренно добавим к window новые свойства. Поэтому лучше использовать классы, так как мы не можем вызывать классы без new. Функции конструктора и классы эквивалентны.

Читайте также:


Перевод статьи John Au-Yeung: What is ‘this’ in JavaScript?

Предыдущая статьяО дивный читаемый код
Следующая статья3 инструмента, чтобы начать программировать на недорогом гаджете