Для начинающих программистов и опытных программистов, пришедших из других языков, ключевое слово this
в Javascript обычно кажется немного запутанным, и многим людям сначала трудно понять это.
Но больше не беспокойтесь. Это руководство поможет вам!
Заявление об ограничении ответственности:
- Я предполагаю, что вы знакомы с основными концепциями программирования в целом (переменные, объекты, функции, методы), а также с синтаксисом Javascript (ES6).
- Все упоминания функций относятся к классическим функциям Javascript, а не к функциям стрелок.
- Я настоятельно рекомендую вам не только прочитать это руководство, но и самостоятельно проработать примеры кода. Поиграйте с ними, чтобы лучше понять.
Контекст в реальном мире
Давайте сначала поговорим о контексте. Подумайте, что означает контекст на английском языке. Для многих типов утверждений контекст важен для их понимания. Предложение «Мне это нравится!» невозможно полностью понять, если вы не знаете, о чем говорит человек. «Это» может относиться практически ко всему - человек может есть что-нибудь вкусненькое, сидеть в хорошей машине или читать книгу. Если вы ничего не знаете о ситуации, в которой было сделано заявление (т. Е. О контексте), вы не можете вывести из него какую-либо значимую информацию.
«This» требует контекста - независимо от того, используется ли оно в разговорной речи или языке программирования.
Глобальный объект в javascript
Учтите следующее:
console.log(1)
Очевидно, этот оператор выводит 1
на консоль. Но откуда на самом деле взялась команда console.log()
? Вы нигде не определили его, но можете использовать.
В Javascript есть нечто, называемое «глобальным объектом». Он встроен в сам язык и предоставляет всевозможные функции и переменные - среди прочего, объект console
с методом log()
.
Глобальный объект выглядит так же, как любой другой объект Javascript. Рассмотрим объект, выглядящий так:
const globalObject = { console: { log: function(argument) { // log something into the console } } }
Теперь, чтобы использовать метод ведения журнала только что созданного globalObject, вы должны просто сделать:
globalObject.console.log(1)
Поскольку встроенный глобальный объект Javascript доступен в любое время, вы можете просто сделать:
console.log(1)
Нет необходимости явно указывать Javascript, что console
является свойством глобального объекта - он всегда там.
Что это?
Все идет нормально. Теперь перейдем к this
Javascript!
Рассмотрим эту функцию:
const logThis = function() { console.log(this) }
Теперь попробуйте угадать, что будет записано при вызове logThis()
. Как вы думаете, this
будет?
Ты угадал! Это глобальный объект Javascript! Как видите, logThis()
возвращает объект с очень знакомыми свойствами, который выглядит примерно так:
{ console: [Getter], setTimeout: [Function], clearInterval: [Function], ... }
Это все функции, которые вы можете использовать изначально в Javascript без необходимости их определять, поскольку они предоставляются вам глобальным объектом по умолчанию.
Это означает, что контекстом по умолчанию в Javascript является глобальный объект! Если не указано иное, this
всегда относится к глобальному объекту.
Контекстом Javascript по умолчанию является глобальный объект. Таким образом, значение по умолчанию
this
является ссылкой на глобальный объект.
Еще примеры
const logConsoleObject = function() { console.log(this.console) }
console
- это метод глобального объекта. Поскольку this
относится к глобальному объекту, this.console
относится к объекту консоли, а вывод logConsoleObject
выглядит примерно так:
Console { log: [Function], debug: [Function], info: [Function], warn: [Function], ... }
И поскольку this
относится к глобальному объекту, а консоль является свойством глобального объекта, эти две функции делают одно и то же:
const log1 = function(something) { console.log(something) // console is provided natively by the global object } const log2 = function(something) { this.console.log(something) // this refers to the global object }
log1("abc")
и log2("abc")
оба регистрируют abc
в консоли, поскольку они оба используют один и тот же метод console.log
, предоставляемый глобальным объектом.
Переосмысление этого
Хватит уже глобального объекта. Давайте перейдем к «нормальным» объектам.
Давайте определим объект по имени Питер:
const peter = { name: “Peter”, sayName: function() { return this.name }, } peter.sayName() // "Peter"
Теперь давайте определим эту же функцию не как свойство peter
, а глобально:
const sayName = function () { return this.name } sayName() // undefined
Возвращаемые значения peter.sayName()
и sayName()
различаются, хотя код для двух функций идентичен. Они оба возвращают this.name.
Почему? Потому что this
изменился!
В sayName()
this
относится к глобальному объекту. Глобальный объект не имеет свойства name
, следовательно, это undefined
. Однако в peter.sayName()
this
относится к объекту peter
! И у Питера есть свойство name.
Создайте две функции, которые вместо этого не возвращают this.name
, а только this
, и вы увидите, что returnThis()
возвращает глобальный объект, а peter.returnThis()
возвращает объект peter
! Фактически peter.returnThis
идентичен peter
.
const returnThis = function () { return this } const peter = { name: “Peter”, sayName: function() { return this.name }, returnThis: function() { return this } } returnThis() // the global object peter.returnThis() // the peter object peter.returnThis() === peter // true peter.name // "Peter" peter.returnThis().name // "Peter"
Если
this
используется в функции, он всегда относится к контексту, в котором функция вызывается.
Посмотрим, что произойдет, если peter.sayName
переназначить новой переменной sayPeterName
:
peter.sayName() // "Peter" const sayPeterName = peter.sayName sayPeterName() // undefined
На первый взгляд sayPeterName()
несколько загадочным образом возвращает undefined. Однако причина такого поведения довольно проста.
Как указывалось ранее, коды peter.sayName
и sayName
точно такие же. Это означает, что
const sayPeterName = peter.sayName
точно так же, как
const sayPeterName = function () { return this.name }
Это в точности совпадает с нашей исходной функцией sayName
с самого начала!
В sayPeterName
this
снова обращается к глобальному объекту! Следовательно, свойство name
не возвращается. sayPeterName
и sayName
идентичны, но sayPeterName
и peter.sayName
- нет, поскольку они вызываются в другом контексте - их this
не одинаковы!
Давайте свяжем вещи!
Так как же вы могли сделать так, чтобы sayPeterName
действительно вернул имя Питера?
Проблема здесь в первую очередь в том, что this
в sayName()
не относится к объекту peter
. Помните:
const sayPeterName = peter.sayName = function () { return this.name }
sayPeterName
больше не является собственностью объекта peter. Он не «привязан» к peter
, следовательно, его контекст - это глобальный объект, а не объект peter
.
Здесь пригодится bind
метод Javascript. bind
можно использовать для каждой функции. Это позволяет вам явно указать контекст, в котором должна вызываться функция.
const sayPeterName = sayName.bind(peter) sayName() // undefined sayPeterName() // "Peter"
Так что здесь происходит? sayName()
по-прежнему просто возвращает this.name
. Привязка позволяет вам сказать Javascript: «Эй, всякий раз, когда вызывается sayPeterName
, используйте тот же код, что и sayName
, но с объектом peter
в качестве контекста, так что this
относится к peter
!».
Теперь peter.sayName()
и sayPeterName()
возвращают одно и то же значение, потому что у них не только одинаковый код, но и одинаковый контекст!
Таким же образом вы могли бы сделать:
const anna = { name: “Anna” } const sayAnnaName = sayName.bind(anna) sayAnnaName() // "Anna"
bind
- это не какая-то сложная волшебная операция. Все, что он делает - это явно присваивает функции определенный контекст!
Вывод
В Javascript this
- это ссылка на контекст, в котором функция вызывается. Если функция еще не привязана к определенному контексту (например, при определении функции как метода для объекта), по умолчанию будет использоваться глобальный объект. bind
можно использовать для явного определения контекста функции.
const returnThis = function () { return this } const sayName = function () { return this.name } const peter = { name: “Peter”, sayName: function() { return this.name }, returnThis: function() { return this } } returnThis() // the global object peter.returnThis() // the peter object sayName() // undefined peter.sayName() // "Peter" let sayPeterName = peter.sayName sayPeterName() // "undefined" sayPeterName = peter.sayName.bind(peter) sayPeterName() // "Peter"
Для дальнейшего чтения я рекомендую руководство Mozilla по контексту функции: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#Function_context