Для начинающих программистов и опытных программистов, пришедших из других языков, ключевое слово 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