Ключевое слово this
Поведение ключевого слова this
зависит от контекста, в котором оно используется, и от того, в каком режиме оно используется — строгом или нестрогом.
Существует четыре основных контекста, в которых можно неявно определить значение ключевого слова this
:
- Глобальный контекст
- Как метод внутри объекта
- Как конструктор в функции или классе
- Как обработчик событий DOM
Глобальный контекст
В глобальном контексте this
ссылается на глобальный объект. Когда вы работаете в браузере, глобальный контекст — это окно. Когда вы работаете в Node.js, глобальный контекст — это global
.
Если вы зарегистрируете значение this
без какого-либо другого кода:
console.log(this)
Вы увидите к какому объекту относится this
:
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Функция верхнего уровня сохранит ссылку this
на глобальный объект. Для примера давайте напишем функцию верхнего уровня или функцию, которая не связана ни с одним объектом:
function printThis() {
console.log(this)
}
printThis()
Результат работы:
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Даже внутри функции this
все равно относится к window
, или к глобальному объекту.
Однако при использовании строгого режима контекст this
внутри функции в глобальном контексте будет undefined
:
'use strict'
function printThis() {
console.log(this)
}
printThis()
Результат работы:
undefined
Как правило, строгий режим использовать безопаснее, так как он позволяет уменьшить вероятность непредвиденной области применения ключевого слова this
. Вряд ли кто-то захочет обратиться к объекту window
, используя this
.
Метод объекта
Метод — это функция объекта или задача, которую может выполнить объект. Метод использует this
для ссылки на свойства объекта.
const america = {
name: 'The United States of America',
yearFounded: 1776,
describe() {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
},
}
america.describe()
Результат работы:
"The United States of America was founded in 1776."
В этом примере this
, будет указывать america
.
Во вложенном объекте this
ссылается на текущую область метода. В следующем примере this.symbol
в объекте details
ссылается на details.symbolcode>
:
const america = {
name: 'The United States of America',
yearFounded: 1776,
details: {
symbol: 'eagle',
currency: 'USD',
printDetails() {
console.log(`The symbol is the ${this.symbol} and the currency is ${this.currency}.`)
},
},
}
america.details.printDetails()
Результат работы:
"The symbol is the eagle and the currency is USD."
Проще говоря, this
ссылается на объект с левой стороны от точки при вызове метода.
Конструктор функций
Когда вы используете ключевое слово new
, оно создает экземпляр функции или класса конструктора. Конструкторы функций были стандартным способом инициализации пользовательского объекта до того, как в 2015 вместе с обновлением ECMAScript
для JavaScript
появился синтаксис класса.
function Country(name, yearFounded) {
this.name = name
this.yearFounded = yearFounded
this.describe = function() {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
}
}
const america = new Country('The United States of America', 1776)
america.describe()
Результат работы:
"The United States of America was founded in 1776."
В этом контексте this
ссылается на экземпляр Country
, который содержится в константе america
.
Конструктор класса
Конструктор в классе действует так же, как в функции:
class Country {
constructor(name, yearFounded) {
this.name = name
this.yearFounded = yearFounded
}
describe() {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
}
}
const america = new Country('The United States of America', 1776)
america.describe()
Ключевое слово this
в методе describe
относится к экземпляру Country
, которым является america
.
Результат работы:
"The United States of America was founded in 1776."
Обработчик событий DOM
В браузере есть специальный контекст this
для обработчиков событий. В обработчике событий, вызываемом addEventListener
ключевое слово this
будет ссылаться на event.currentTarget
. Чаще всего по мере необходимости разработчики просто используют event.target
или event.currentTarget
для доступа к элементам в DOM
, но так как ссылка this
изменяется в этом контексте, это важно знать.
В следующем примере мы создадим кнопку, добавим к ней текст и поместим ее в DOM
. Когда мы записываем значение this
в обработчик события, он выводит цель.
const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)
button.addEventListener('click', function(event) {
console.log(this)
})
Как только вы вставите этот код в свой браузер, на странице появится кнопка с надписью Click me
. Если вы нажмете кнопку, вы увидите Click me
в консоли, так как нажатие кнопки регистрирует элемент, который является самой кнопкой. Как видите, this
ссылается на целевой элемент, который является элементом, к которому мы добавили прослушиватель событий.
Явный контекст
Во всех предыдущих примерах значение this
определялось его контекстом: глобальным, в объекте, в функции, в классе, в обработчике событий. Используя встроенную функцию call
, apply
или bind
, вы можете явно определить, на что ссылается this
.
Разница между методами call
, apply
и bind
заключается в следующем:
- Методы
call
иapply
вызывает функцию с заданным контекстом сразу. Это позволяет явно указывать, в каком контексте должна выполняться функция. Методы часто используется для наследования методов одного объекта другим - Метод
Bind
не вызывает функцию сразу. Он возвращает новую функцию, у которой заранее привязан контекстthis
, а также заданные аргументы. Методbind
полезен, когда нужно заранее привязать контекст и передать функцию, которая будет вызвана позже
Таким образом, call
и apply
обеспечивает немедленное выполнение функции с различными форматами аргументов, а bind
создаёт многоразовые функции с фиксированными контекстами
Методы call и apply
Методы call
и apply
очень похожи, они вызывают функцию с указанным контекстом this
и дополнительными аргументами. Единственная разница между call
и apply
заключается в том, что call
требует, чтобы аргументы передавались по одному, а apply
принимает их в виде массива.
В этом примере мы создадим объект и функцию, которая ссылается на this
, но не имеет контекста this
:
const book = {
title: 'Brave New World',
author: 'Aldous Huxley',
}
function summary() {
console.log(`${this.title} was written by ${this.author}.`)
}
summary()
Результат работы:
undefined was written by undefined
Поскольку summary
и book
не связаны, сам по себе вызов summary будет выводить только неопределенное значение undefined
, так как он ищет эти свойства в глобальном объекте. Попытка сделать это в строгом режиме приведет к Uncaught TypeError: Cannot read property ‘title’ of undefined
, так как само ключевое слово this
будет undefined
.
Тем не менее, вы можете использовать call
и apply
для вызова контекста this
для book
в функции:
summary.call(book)
Или:
summary.apply(book)
Результат работы:
Brave New World was written by Aldous Huxley.
Теперь между book
и summary
существует связь. Давайте точно узнаем, на что ссылается this
:
const book = {
title: 'Brave New World',
author: 'Aldous Huxley',
}
function printThis() {
console.log(this)
}
printThis.call(book)
// или
printThis.apply(book)
Результат работы:
{title: "Brave New World", author: "Aldous Huxley"}
В этом случае this
фактически становится объектом, переданным в качестве аргумента.
Как мы уже говорили, call
и apply
почти одинаковы, но есть одно небольшое отличие. Помимо возможности передавать контекст this
в качестве первого аргумента, вы также можете передавать apply
дополнительные аргументы:
function longerSummary(genre, year) {
console.log(
`${this.title} was written by ${this.author}. It is a ${genre} novel written in ${year}.`
)
}
При использовании call
каждое дополнительное значение, которое вы хотите передать, отправляется в качестве дополнительного аргумента:
longerSummary.call(book, 'dystopian', 1932)
Результат работы:
Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932.
Если вы попытаетесь отправить те же аргументы с помощью apply
, произойдет ошибка. При использовании apply
вы должны передать все аргументы в массиве:
longerSummary.apply(book, ['dystopian', 1932])
Результат работы:
Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932.
Метод bind
call
и apply
являются одноразовыми методами, если вы вызываете метод с контекстом this
, он примет его, но исходная функция останется неизменной.
В отдельных ситуациях вам может понадобиться несколько раз использовать метод с контекстом this
другого объекта. В таком случае вы можете использовать метод bind
для создания новой функции с явно привязанным this
:
const book = {
title: 'Brave New World',
author: 'Aldous Huxley',
}
function summary() {
console.log(`${this.title} was written by ${this.author}.`)
}
const braveNewWorldSummary = summary.bind(book)
braveNewWorldSummary()
Результат работы:
Brave New World was written by Aldous Huxley
Каждый раз, когда в этом примере вы вызываете braveNewWorldSummary
, он будет возвращать исходное значение this
, привязанное к нему. Попытка связать с ним новый контекст this
не удастся, поэтому вы всегда можете доверять связанной функции и получить ожидаемое значение this
:
const book = {
title: 'Brave New World',
author: 'Aldous Huxley',
}
function summary() {
console.log(`${this.title} was written by ${this.author}.`)
}
const braveNewWorldSummary = summary.bind(book)
// Brave New World was written by Aldous Huxley.
braveNewWorldSummary()
const book2 = {
title: '1984',
author: 'George Orwell',
}
braveNewWorldSummary.bind(book2)
// Brave New World was written by Aldous Huxley.
braveNewWorldSummary()
this и стрелочные функции
Стрелочные функции не имеют привязки this
. Вместо этого они переходят на следующий уровень исполнения.
const whoAmI = {
name: 'Leslie Knope',
regularFunction: function() {
console.log(this.name)
},
arrowFunction: () => {
console.log(this.name)
},
}
// "Leslie Knope"
whoAmI.regularFunction()
// undefined
whoAmI.arrowFunction()
Стрелочные функции полезно использовать в тех случаях, когда ключевое слово this
должно ссылаться на внешний контекст. Например, если внутри класса у вас есть прослушиватель событий, вы, вероятно, захотите, чтобы this
ссылалось на какое-то значение в классе.
В этом примере мы создадим и добавим кнопку в DOM
, как мы делали раньше, но у класса будет прослушиватель событий, который будет изменять текстовое значение кнопки при нажатии:
const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)
class Display {
constructor() {
this.buttonText = 'New text'
button.addEventListener('click', event => {
event.target.textContent = this.buttonText
})
}
}
new Display()
Если вы нажмете кнопку, текстовое содержимое изменится на значение buttonText
. Если бы вы не использовали здесь стрелочную функцию, this
было бы равно event.currentTarget
, и вы не смогли бы использовать его для доступа к значению в классе без явного его связывания.