«это» странно: Понимание JavaScript «это» Ключевое слово
Линтон Йе
Последнее обновление:
оглавление
- это не только для классов это определяется во время выполнения
- Вызов функции с принимающим объектом
- Новое ключевое слово
- Другие способы изменить это во время выполнения
- вызов и применение
- bind
- bind всегда выигрывает
- this в функциях обратного вызова
- то и это
- this в стрелочных функциях
- this в стрелочных функциях не может быть изменено
- Quiz
- Назад к компонентам класса React
- Резюме
Однако его поведение может показаться очень странным, особенно если вы знакомы с подобными конструкциями в других языках, например, this
в Java или self
в Python 1 .
Возьмите этот компонент класса React 2 в качестве примера:
class MyComponent extends React. Component { конструктор () { супер() это.состояние = {количество: 0} } обработатьклик() { this.setState({ count: this.state.count + 1}) } оказывать() { вернуть } }
Этот код отображает счетчик на кнопке. Выглядит нормально, правда? Но если вы нажмете кнопку, вы получите ошибку (попробуйте в этой песочнице):
TypeError: Не удается прочитать свойство «количество» неопределенного
Это означает, что значение this.state
в handleClick
равно undefined
. Но почему? Разве мы не установили его на { count: 0 }
в конструкторе?
Теперь, если вы добавите console.log(this)
в handleClick
, вы увидите значение this
:
Window {...}
Почему? Разве не должен быть
handleClick
метод экземпляра класса?Это странно!
В этом посте давайте кратко рассмотрим, как этот
работает в JavaScript.
Целевая аудитория
В этой статье предполагается базовое понимание JavaScript. Вы также найдете его полезным, если вы знакомы с другими языками, такими как Java или Python.
В таких языках, как Java, ключевое слово this
имеет смысл только внутри класса. Но в JavaScript мы можем использовать его практически везде:
// Используйте `this` на верхнем уровне, также известном как глобальный контекст console.log(это)
Теперь откройте консоль JavaScript и введите приведенный выше код. Что вы получили?
При использовании на верхнем уровне, также известном как глобальный контекст, значением this
является объект
. Это значение по умолчанию this
3 .
Мы также можем использовать ключевое слово this
в функции, например:
function printThis() { console.log(это) }
Что будет напечатано, если мы вызовем эту функцию? Текущий экземпляр. .. функции? Объект Окно
?
Короткий ответ: мы еще не знаем!
Более длинный ответ:
Значение
этого
внутри функции зависит от способа вызова функции.
Разве это
не странно? Отсюда больше всего путаницы.
Продолжайте читать 👇.
Вызов функции с принимающим объектом
Попробуйте ввести в консоли следующий код:
function printThis() { console.log(это) } распечатать это ()
В этом коде мы получаем значение по умолчанию this
, Window
Теперь попробуйте следующий код:
// определяем функцию как свойство obj пусть объект = { printThis: функция () { console.log(это) }, } obj.printThis ()
Получаем объект, в котором определена функция! Хм, это
интересно.
Но в этом есть смысл, верно? Это позволяет функции легко получить доступ к свойствам объекта, например:
let car = { марка: «Тесла», модель: '3', печать: функция () { console. log(`Производитель: ${this.make}, Модель: ${this.model}`) }, } car.print()
Получится:
Марка: Tesla, Модель: 3
Но это не конец истории! Что, если мы назначим функцию переменной и назовем ее так:
let print = car.print // вызов функции без принимающего объекта Распечатать()
Получим:
Марка: не определено, Модель: не определено
Хм… Функция забывает об объекте, в котором она определена? этот
странный!
Как я уже сказал,
Значение
этого
внутри функции зависит от того, как вызывается функция.
Поскольку мы вызываем функцию print
без принимающего объекта, хотя функция определена внутри объекта автомобиля, значение это
Окно
, значение по умолчанию. У него нет свойств make
и model
.
Теперь небольшая викторина. Как вы думаете, что напечатает следующий код?
функция печати () { console. log(`Производитель: ${this.make}, Модель: ${this.model}`) } пусть грузовик = { марка: «Тесла», модель: «Кибергрузовик», } грузовик.print = печать грузовик.print()
A:
Марка: не определено, Модель: не определено
B:
Марка: Tesla, Модель: Cybertruck
C:
Марка: Tesla, Модель: undefined
D:
Ни один из вышеперечисленных как вызывается функция, НЕ где функция определена.
Разве это
не странно?
новое
ключевое слово Мы также можем вызвать функцию с новым
ключевое слово:
функция Автомобиль(марка, модель) { это.сделать = сделать эта.модель = модель } пусть автомобиль = новый автомобиль ('Tesla', '3') console.log(машина)
Получим:
Автомобиль {марка: "Tesla", модель: "3"}
При вызове функции с ключевым словом new
мы получим новый объект. Значение this
внутри этой функции равно новому объекту.
Следующий код эквивалентен, но в формате класса:
class Car { конструктор (производитель, модель) { // `this` это новый объект это.сделать = сделать эта.модель = модель } } пусть автомобиль = новый автомобиль ('Tesla', '3') console.log(машина)
На самом деле class
в JavaScript — это просто синтаксический сахар. Под капотом это просто функция.
До сих пор я показал вам, что значение это
зависит от того, как вызывается функция:
- Вызывали ли мы функцию с принимающим объектом? то есть
obj.print()
или простоprint()
? - Мы вызвали функцию с
новым ключевым словом
?
Но это далеко не полная картина! Оказывается, мы можем изменить значение это
в функции к тому, что мы хотим — нам даже не нужно использовать принимающий объект.
вызов
и применение
Мы можем вызвать любую функцию, используя вызов
или применение
, чтобы указать значение this
:
function anyFunction() { console. log(это) } anyFunction.call({ msg: 'независимо' }) // Вывод: Объект { msg: "что угодно" } anyFunction.apply({ msg: 'независимо' }) // Вывод: Объект { msg: "что угодно" }
bind
Метод bind
возвращает функцию, которую мы можем вызывать так же, как исходную функцию. Но мы можем быть уверены, что значение это
всегда то, что мы хотим.
функция printThis() { console.log(это) } let print = printThis.bind({ msg: 'Я могу быть любым объектом, который вы хотите!' }) Распечатать()
Вывод:
Object { msg: "Я могу быть любым объектом, который вы хотите!"}
bind
полезен, когда у нас нет контроля над тем, как вызывается функция, например, при передаче функции в качестве обратного вызова. Подробнее об этом чуть позже.
bind
всегда выигрывает Стоит отметить, что пока функция связана, это
внутри функции остается привязанным значением. Его больше нельзя изменить с помощью , вызова
или , применения
или даже принимающего объекта.
функция printThis() { console.log(это) } letboundFun = printThis.bind(`Я связан! Вы не можете изменить мое "это"!`!) boundFun.call('ха-ха') // Вывод: я связан! Вы не можете изменить мое "это"! boundFun.apply('лала') // Вывод: я связан! Вы не можете изменить мое "это"! пусть объект = {} obj.printThis = связанное удовольствие obj.printThis () // Вывод: я связан! Вы не можете изменить мое "это"!
В JavaScript мы можем передавать функцию как любое другое значение. Функция обычно называется функцией обратного вызова , поскольку мы сами не вызываем ее напрямую, она будет вызвана обратно другой частью кода чуть позже.
Поскольку у нас нет контроля над тем, как вызывается функция обратного вызова — ее можно вызвать напрямую или через вызов
или применить
— мы не можем принять значение this
внутри функции обратного вызова . Это может быть что угодно, правда.
В приведенном ниже коде значение
отличается в каждой функции обратного вызова:
let colors = ['красный', 'зеленый', 'синий'] пусть кнопка = документ. getElementById('кнопка') button.addEventListener('щелчок', функция () { // Внутри callback-функции 1: обработчик события // `this` является ссылкой на элемент, по которому щелкнули colors.forEach (функция () { // Внутри callback-функции 2 // `это` значение по умолчанию, `Окно` }) })
этот
и that
В приведенном выше коде, если мы хотим использовать значение this
во второй функции обратного вызова, нам нужно сохранить его значение в переменной внешней функции.
На самом деле это становится обычным шаблоном:
button.addEventListener('click', function () { пусть это = это colors.forEach (функция () { console.log(это) // Вывод: кнопка }) })
ES6 представляет стрелочные функции, которые позволяют нам использовать более простой синтаксис для определения функции, т. е. стрелку =>
вместо функции ключевое слово
:
пусть весело = () => { console. log("Я в функции стрелки!") }
Какое значение это
внутри стрелочной функции? Это та же история, что и в «нормальной» функции?
Нет! В стрелочной функции поведение и
полностью противоположно!
В стрелочной функции значение
этого
зависит от , где функция определена, а НЕ от того, как функция вызывается!
Значение this
в стрелочной функции такое же, как и в закрывающем блоке функции. Итак, нам больше не нужен танец то-то в стрелочных функциях:
button.addEventListener('click', function () { // пусть это = это // ==> Больше не нужно! console.log(это) // Вывод: кнопка colors.forEach(() => { // В стрелочной функции this равно значению this во внешней области видимости console.log(это) // Вывод: кнопка }) })
это
в стрелочных функциях не может быть изменено Опять же, значение это
в стрелочной функции зависит от , где определена функция. Поэтому его нельзя изменить во время выполнения!
Это означает, что ни один из bind
, call
или apply
не влияет на функции стрелок. Предоставленное это значение
просто игнорируется.
пусть arrowFun = () => { console.log(это) } arrowFun.call('Измени это!') // Вывод: окно arrowFun.apply('Измени это!') // Вывод: окно пусть boundArrowFun = arrowFun.bind('Изменить!') границаСтрелкаFun() // Вывод: окно
В приведенном выше коде всегда выводится Window
, поскольку функция стрелки определена в глобальном контексте, где это
получает значение по умолчанию, Window
.
Вам может быть интересно узнать о новом ключевом слове
. Изменит ли это значение на
?
Ответ НЕТ! На самом деле, вы даже не можете вызвать стрелочную функцию с new
.
пусть arrowFun = () => { console.log(это) } новая стрелкаFun() // Uncaught TypeError: arrowFun не является конструктором
Тест
А как насчет быстрого теста? Каков результат кода ниже?
пусть автомобиль = { марка: «Тесла», модель: '3', напечатать: () => { console. log(`Производитель: ${this.make}, Модель: ${this.model}`) }, } car.print()
A:
Make: Undefined, модель: не определено
B:
Make: Tesla, Модель: 3
C:
Make: Tesla, модель: не определено
D.
Ошибка типа
Давайте вернемся к примеру компонента класса, который я показал вам в начале. Теперь вы понимаете, почему мы получаем сообщение об ошибке, когда пользователь нажимает кнопку? Почему значение этого
равно Window
вместо объекта экземпляра внутри handleClick
?
класс MyComponent расширяет React.Component { конструктор () { супер() это.состояние = {количество: 0} } обработатьклик() { this.setState({ count: this.state.count + 1}) } оказывать() { вернуть } }
Что из следующего НЕ является причиной ошибки?
A:
Функция «handleClick» передается кнопке и вызывается без принимающего объекта.
B:
Значение по умолчанию «this» внутри handleClick равно «Window».
C:
Значение «this» всегда равно «Window» внутри класса.
Как исправить ошибку в вышеупомянутом компоненте класса? Как бы мы убедились, что значение это
в handleClick
текущий экземпляр класса вместо Window
? Другими словами, есть ли способ явно установить значение для этого
?
Ответ: bind
:
class MyComponent extends React.Component { конструктор () { супер() это.состояние = {количество: 0} // `this` в конструкторе всегда является только что созданным новым объектом. this. handleClick = this.handleClick.bind(это) } обработатьклик() { this.setState({ количество: количество + 1}) } оказывать() { вернуть } }
Еще одно более чистое решение IMO — использовать функцию стрелки:
class MyComponent extends React.Component { конструктор () { супер() это.состояние = {количество: 0} } // Определяем handleClick как стрелочную функцию handleClick = () => { // `this` равно значению окружающего блока. // В данном случае это экземпляр объекта. this.setState({ count: this.state.count + 1}) } оказывать() { вернуть } }
Хорошо, выше показано, как это
работает в JavaScript. Вам все еще кажется это странным? ИМО чувствует себя намного лучше, если мы понимаем правила под капотом. Вот резюме:
-
этот
предназначен не только для занятий. Его можно использовать глобально, в функции или в классе. - В «обычных» функциях значение
этого
зависит от того, как вызывается во время выполнения.- Мы можем изменить значение
на этот
с помощью вызоваприменить
илисвязать
.
- Мы можем изменить значение
- В стрелочных функциях
и
совершенно противоположны. Значениеэтого
зависит от , где функция определена.- Значение
этого
НЕ МОЖЕТ быть изменено во время выполнения.
- Значение
- В компоненте класса React мы можем использовать
bind
или стрелочную функцию, чтобы гарантировать, что значениеthis
будет объектом экземпляра класса. Это делает поведениеэтот
больше соответствует другим языкам.
Чтобы узнать больше, ознакомьтесь с этой статьей MDN и этим ответом StackOverflow (более 1300 голосов!).
- Здесь моя собственная история. Я так долго боролся с
и
в JavaScript. Это именно из-за моего опыта работы с Java — у меня с самого начала была неправильная ментальная модель. ↩ - Я знаю, в наши дни мы больше не пишем столько компонентов класса в React. Но все же полезно понимать их, поскольку вы обычно видите их в устаревшем коде. Не у всех есть время переписать все компоненты класса как функциональные компоненты.↩
- Поскольку JavaScript может работать в среде, отличной от веб-браузера, существует специальное ключевое слово
globalThis
, которое указывает наWindow
или другое значение в зависимости от среды. undefined или другие значения в строгом режиме.↩
Посещений:
0
Надеюсь, эта статья окажется для вас полезной!
Одна из моих целей на 2021 год — написать больше постов, которых будет 9.0089 полезные , интерактивные и развлекательные . Хотите получать ранние предварительные просмотры будущих сообщений? Зарегистрируйтесь ниже. Никакого спама, отпишитесь в любое время.
Вам также может понравиться
React useRef Hook By Example: A Complete Guide
Обновлено:
react
useRef
— это встроенный React Hook. Для чего это используется? Как это работает? Я раскрою вам эту скрытую жемчужину на нескольких реальных примерах.
Подробнее
Ментальные модели React: Работа с вводом
Обновлено:
react
Как мы работаем с вводом HTML в React? Его поведение может быть удивительным. Прочитайте этот пост, чтобы получить хорошее представление о Input. И вы также сможете выполнять некоторые упражнения кунг-фу с Пандой.
Подробнее
React Mental Models: истинное лицо JSX
Обновлено:
react
Что такое тег JSX на самом деле? Это замаскированный код JavaScript. Откроем его истинное лицо!
Подробнее
Как работает JavaScript: переменная this и контекст выполнения | by Lawrence Eagles
Это пост №32 из серии, посвященной изучению JavaScript и его строительных компонентов. В процессе определения и описания основных элементов мы также поделимся некоторыми практическими правилами, которые мы используем при создании SessionStack, приложения JavaScript, которое должно быть надежным и высокопроизводительным, чтобы помочь компаниям оптимизировать цифровой опыт своих пользователей.
this переменная или ключевое слово — очень загадочная и мощная функция языка программирования JavaScript. Его часто боятся новички в JavaScript, и он может даже создать некоторые проблемы для опытных разработчиков. Это связано с непредсказуемостью объекта, на который он ссылается в данном контексте.
Хорошее понимание этой переменной требуется для точного предсказания того, на какой объект она ссылается в любом контексте. Как только это приобретено, оно становится бесценным инструментом в руках разработчика JavaScript.
эта переменная в JavaScript ведет себя иначе, чем в других языках программирования. Хотя он относится к объекту в данном контексте, его поведение непоследовательно. Следовательно, это трудно понять.
В этой статье мы узнаем о поведении переменной this в различных контекстах выполнения в JavaScript.
Однако, чтобы понять эту переменную в JavaScript, нам необходимо понимание некоторых фундаментальных концепций JavaScript, таких как контекст выполнения и стек выполнения, также называемый стеком вызовов.
Давайте узнаем об этих фундаментальных аспектах JavaScript в следующем разделе:
Каждый бит нашего кода в JavaScript выполняется в контексте выполнения.
Контекст выполнения является оболочкой для текущего исполняемого кода.
В двух словах, контекст выполнения — это абстрактный контекст, который содержит информацию об окружении исполняемого в данный момент кода.
Когда механизм JavaScript начинает выполнение файла JavaScript, то есть когда механизм JavaScript начинает читать наш код, он создает контекст выполнения, даже если в файле JavaScript нет кода для выполнения.
Рассмотрим изображение ниже:
Изображение выше иллюстрирует глобальный контекст выполнения. Он состоит из следующего:
- Глобальный объект. В браузере это объект окна.
- это переменная . Каждый контекст выполнения предоставляет переменную this , которая ссылается на объект, которому принадлежит исполняемый в данный момент код.
- Окружение переменных — место в памяти, где живут переменные.
- Внешняя среда. Когда мы выполняем код внутри функции, внешней средой является код вне этой функции — на глобальном уровне он равен нулю.
Кроме того, контекст выполнения создается в два этапа, а именно:
- Фаза создания: на этой стадии движок JavaScript проходит через наш код и добавляет все переменные и функции в память.
- Этап выполнения: На этом этапе значения присваиваются переменным и вызываются функции.
При каждом вызове функции создается новый контекст выполнения, который добавляется на вершину стека выполнения.
Стек выполнения — это стек со структурой LIFO (последним поступил — первым обслужен), в котором хранятся все контексты выполнения, созданные во время выполнения кода.
Стек выполнения позволяет движку JavaScript отслеживать порядок выполнения.
Давайте подробнее рассмотрим некоторые примеры кода.
Рассмотрим код ниже:
Когда приведенный выше код запускается, создается глобальный контекст выполнения, и наш код выполняется синхронно. Оператор console.log в глобальном контексте выполнения выполняется, и «hello from global» регистрируется в консоли.
Затем выполняется вызов функции a() , который создает новый контекст выполнения и добавляет его в начало стека выполнения. Код в этой функции теперь будет выполняться синхронно.
Здесь первые console.log Заявление достигнуто ведение журнала «функция a выполняется» . Затем движок JavaScript сталкивается с другим вызовом функции — вызовом b() , и это создает новый контекст выполнения и добавляет его в начало выполнения — до того, как код в функции a() будет полностью выполнен.
Следовательно, код в функции b() будет выполнен. Таким образом, «функция b выполняется» и «функция b() завершена» будет записано в консоль.
Когда механизм JavaScript завершает выполнение функции b() , контекст выполнения извлекается из стека выполнения, а затем выполняется оставшийся код в функции a() , таким образом регистрируется «функция a завершена в консоль.
Изображение ниже иллюстрирует поток выполнения приведенного выше примера кода:
Зная эти две концепции, мы теперь можем погрузиться в поведение эта переменная в разных контекстах в JavaScript.
Глобальный контекст выполнения:Глобальный контекст выполнения является базовым контекстом выполнения. Это относится к контексту выполнения, созданному по умолчанию, когда движок JavaScript начинает читать наш код.
Когда мы говорим глобальный, мы имеем в виду что-то, что не находится внутри функции, поэтому оно доступно везде в нашем коде.
Глобальный контекст выполнения создает глобальный объект и специальную переменную, называемую это переменная .
В глобальном контексте выполнения эта переменная ссылается на глобальный объект.
Рассмотрим код ниже:
Контекст выполнения функции:Контекст выполнения функции аналогичен глобальному контексту выполнения. Он также проходит фазу создания и выполнения и имеет свою переменную среду и эту переменную .
Внутри функции this 9Переменная 0092 ссылается на объект в зависимости от того, как вызывается функция.
Если значение this не задано вызовом, значение this относится к глобальному объекту — объекту окна в браузерах.
Однако в строгом режиме значение this равно undefined , если оно не установлено при входе в контекст выполнения.
Рассмотрим код ниже:
Контекст функционального метода:В приведенном выше примере значение this в функции b() равно undefined , потому что функция b() была вызвана напрямую, а не как метод — свойство объекта.
Когда вызывается метод — функция объекта, привязка this устанавливается к объекту. Таким образом, если мы вызовем функцию b() выше как свойство объекта окна, мы не получим undefined .
Рассмотрим код ниже:
То же верно для любого объекта JavaScript, как показано ниже:
Обратный вызов:Рассмотрим код ниже
В приведенном выше коде значение переменной this в наборе 8 функция обратного вызова undefined . Это выявляет несоответствие в поведении переменной this в JavaScript.
Хотя мы продемонстрировали, что при вызове метода это значение относится к объекту, содержащему этот метод. Здесь это правило не работает.
Есть три способа получить доступ к этому в функции обратного вызова выше, а именно:
- Явная установка этого:
Это старый совет. До ES6 и стрелочных функций разработчики хранили ссылку на этот в переменной. Затем переменная используется во всех областях, где они хотят получить доступ к этому . Это избавляет их от стресса, связанного с необходимостью догадываться, что это указывает в любом данном контексте.
Рассмотрим код ниже
Мы уже отмечали, что в методе этот указывает на объект, содержащий этот метод. Таким образом, в методе sayName this указывает на объект Person. Однако в обратном вызове setTimeout это равно undefined . Чтобы получить доступ к свойствам объекта Person в обратном вызове setTimeout , мы сохраняем ссылку this в методе sayName в переменную с именем self .
self затем используется в обратном вызове.
2. Использование функции стрелки
Это самый простой и рекомендуемый способ решения проблемы. Поведение this переменной в объявлениях функций ES5 может быть очень трудно предсказать. Это может привести к ошибкам, которые трудно устранить.
ES6 стрелка — лямбда-функции не имеют это 9привязка 0092. Подразумевается, что это просматривается в области видимости как обычная переменная. Таким образом, решение состоит в том, чтобы использовать функцию стрелки в качестве обратного вызова setTimeout.
Рассмотрим код ниже
3. Использование привязки
Это наиболее техническое из трех решений.
Представленный в ES 5 метод .bind() является очень мощным методом JavaScript, особенно в парадигме функционального программирования.
Прежде чем решать задачу этим методом, рассмотрим .связать() .
Давайте начнем с рассмотрения кода ниже:
Метод bind() позволяет нам явно связать с этим . При вызове функции он привязывает значение this к своему первому аргументу и возвращает новую копию этой функции.
В приведенном выше коде bind() привязывает значение this к объекту разработчика и возвращает новую функцию, хранящуюся в переменной boostedLogDevName .
Таким образом, мы можем получить доступ к методу getDevName из функции boostedLogDevName .
Точно так же мы можем решить нашу задачу обратного вызова, явно привязав этот к объекту Person, как показано ниже:
Конструктор функциив конструктор.
Также оператор new создает новый объект и привязывает эту переменную к объекту.
Рассмотрим код ниже:
КлассыПодобно обычным функциям — поскольку класс JavaScript является специальной функцией, значение this в методе зависит от того, как он вызывается.
Однако, чтобы всегда ссылаться на экземпляр класса, общепринятым шаблоном является привязка метода класса в конструкторе.
Рассмотрим код ниже:
И
В приведенных выше примерах мы видим, что без явной привязки это , контекст метода printName меняется. Следовательно, наш код выдает ошибку.
Поведение этой переменной в JavaScript довольно загадочно. Следовательно, это аспект JavaScript, который трудно освоить. Несмотря на это, следуя рекомендациям из нашего обсуждения выше, разработчик должен иметь возможность использовать с этим практически без проблем.
Кроме того, ниже приведены некоторые рекомендации, которым необходимо следовать при использовании это в JavaScript.
Имеет разные значения в зависимости от того, где используется:
- В методе этот относится к объекту-владельцу.
- Один, этот относится к глобальному объекту.
- В функции этот ссылается на глобальный объект.
- В функции в строгом режиме этот не определен.
- В событии этот относится к элементу, получившему событие.
- Такие методы, как call() и apply(), могут ссылаться на этот на любой объект.
Довольно часто ошибки, связанные с неправильным использованием этой переменной , попадают в производство. Особенно в тех случаях, когда код сильно асинхронен. Эти ошибки часто очень трудно устранить, поскольку они могут сильно зависеть от контекста, а трассировка стека в таких сценариях может быть весьма ограниченной.