Значение this - одна из самых запутанных составляющих JavaSсript. this может принимать различные значения в зависимости от того, где располагается.
В этой статье мы рассмотрим, где используется ключевое слово this и какое значение оно может принимать.
Верхний уровень
Если мы используем this на верхнем уровне, то в качестве его значения должны получить глобальный объект, так как this не находится внутри какого-либо хост-объекта.
К примеру, если у нас есть console.log(this) на верхнем уровне скрипта браузера, то мы должны увидеть объект window в качестве значения this. Можем подтвердить это, использовав:
console.log(this === window) // true
Внутри функций верхнего уровня
Если мы определяем обычную функцию и ссылку this, то в таком случае можем получить два различных значения в зависимости от того, включен ли для нашего скрипта строгий режим.
this принимает значение window, если скрипт не находится в строгом режиме. Однако, если это не так, то значение будет undefined.
Например, если у нас имеется:
'use strict'; function foo() { console.log(this); } foo(); // undefined
То мы увидим, что this принимает значение undefined.
И напротив, в случае:
function foo() { console.log(this); } foo(); // window
Видим, что значение 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()); // Jane Smith console.log(alex.fullName()); // Alex Smith
В коде, который показан сверху, у нас присутствует обычная функция fullName, которая ссылается на this.firstName и this.lastName и возвращает их комбинацию.
Внутри конструкторов
В JavaScript конструкторы - это функции, которые используют оператор new для создания нового экземпляра и изменения свойств this.
Например, если мы запустим следующий код без использования строгого режима:
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } Person('Joe', 'Smith'); const { firstName, lastName } = window; console.log(firstName, lastName); // Joe Smith
Тогда в качестве вывода 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); // Joe Smith
... тогда мы получим ошибку "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(); // window
В следующем примере мы получим 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()); // undefined console.log(alex.fullName()); // undefined
Как видим, стрелочные функции не устанавливают значения this.firstName и this.lastName. this - это просто window, а не хост-объект, так что и то, и другое будет undefined.
Заключение
this принимает значение хост-класса или хост-объекта, если находится внутри метода, определенного с помощью традиционной функции. Стрелочные функции не связаны с this, поэтому мы не можем ссылаться на this, как это было с традиционными функциями.
Случайно вызвать функции конструктора, как обычную функцию, легко. Если это произойдет без строгого режима, то мы ненамеренно добавим к window новые свойства. Поэтому лучше использовать классы, так как мы не можем вызывать классы без new. Функции конструктора и классы эквивалентны.