Мастер JavaScript: что случилось с этим?
Печально известное ключевое слово this
- одна из вещей в JavaScript, которая, кажется, сбивает разработчиков с толку больше, чем что-либо другое. Чаще всего this
кажется и непредсказуемым, и нелогичным, и, конечно, не так, как в большинстве других языков программирования.
Если вы когда-либо дергали себя за волосы из-за this
, вы определенно не одиноки.
Сможете ли вы понять, что напечатано из приведенного выше фрагмента?
Вот спойлер: Hi, I'm undefined, and I'm a undefined
Судя по внешнему виду кода, вероятно, не так, как предполагалось.
Почему это происходит? Потому что this
где-то заблудился.
Так что с this
?
В этой статье я объясню, как this
становится связанным и как задать себе 4 вопроса, из которых вы всегда можете определить, на что указывает this
в любом заданном контексте.
Лексическая область видимости против динамической области видимости
Во-первых, нам нужно понять, как работает область видимости в JavaScript.
Как и в большинстве языков программирования, в JavaScript используется лексическая область видимости.
Это означает, что на переменную или функцию можно ссылаться только из той области видимости, в которой она объявлена.
Области действия строго вложены друг в друга, что также означает, что на переменную или функцию можно ссылаться из внутренней области. < br /> Самое главное, что область действия определяется во время компиляции, следовательно, структура области не изменяется во время выполнения.
Самая внешняя область видимости в JavaScript известна как глобальная scope и имеет ссылку на объект window.
При динамической области видимости область, из которой мы можем ссылаться на объявленные переменные или функцию, изменяется динамически.
Давайте попробуем сделать пример с этим псевдо-JavaScript и представим, что теперь он использует динамическое определение области.
Начиная с dynamicBuilding()
мы пытаемся сослаться на переменную, которая не объявлена ни в своей собственной области, ни в области, которая ее обертывает; глобальная область видимости.
Когда мы просто вызываем эту функцию, она вернет undefined
или выдаст ошибку ссылки. Но если мы объявляем переменную в той же области видимости, из которой мы вызываем функцию, внезапно мы больше не получаем ссылочную ошибку.
Ключевой вывод здесь заключается в том, что dynamicBuilding()
ведет себя по-разному в зависимости от того, где он был вызван from.
Область - или контекст выполнения - откуда вызывается функция, называется местом вызова функции.
NB. Приведенный выше пример - это псевдо-JavaScript. Это не то, как работает JavaScript, и он не будет выполняться, как в примере.
4 способа привязки "this"
Здесь вы можете задаться вопросом, почему я говорю об области видимости в JavaScript, когда эта статья посвящена ключевому слову this
.
Причина в том, что принцип лексической области видимости в JavaScript, который большинство из нас привыкли и находят это совершенно естественным - это основная причина, по которой мы теряемся, когда дело доходит до this
.
JavaScript не использует динамическую область видимости, и никогда не использовал, но в отношении того, как JavaScript выполняет эту привязку, мы на самом деле очень близки к чему-то, что напоминает об этом.
Итак, вот оно:
В JavaScript this
определяется сайтом вызова функции, на которую ссылается this
.
То есть, когдаthis
используется внутри функции, this
будет ссылкой на контекст выполнения, из которого выполняется функция.
Есть 4 разных способа this
привязки в JavaScript, и все они ведут себя по-разному:
- Ключевое слово
new
- Явная привязка
- Неявная привязка
- Привязка по умолчанию
Это также означает, что это 4 ключевых вопроса, которые вы зададите себе, чтобы определить, на что указывает this
в любом данном контексте.
Давайте рассмотрим их один за другим.
"Новое" ключевое слово
При использовании this
в функции первое, что вам нужно спросить себя, это использовать ли вы ключевое слово new
при вызове функции.
При применении ключевого слова new
будет создан экземпляр определяемого пользователем объекта, и this
будет привязан к этому объекту.
Ключевое слово new
будет выполнять следующие 4 действия:
- Создайте пустой простой объект JavaScript.
- Свяжите этот объект с другим объектом.
- Передайте вновь созданный объект из шага 1 как
this
контекст. - Вернуть
this
, если функция не возвращает собственный объект.
Как мы видим в приведенном выше примере, this
будет ссылкой на объект, который будет создан в результате вызова функции thisGuard()
с ключевым словом new
.
В ES6 были введены классы.
Чтобы уточнить: в JavaScript нет таких вещей, как классы - это не что иное, как синтаксический сахар, который делает именно то, что мы видим выше. Следовательно, способ привязки this
работает точно так же с классами.
Следующий фрагмент кода эквивалентен приведенному выше:
Явная привязка
Если это не тот случай, когда вы применили ключевое слово new
, следующее, что нужно спросить себя, это использовать ли вы явную привязку.
Есть несколько способов вызвать функцию и явно определить, на что this
должна ссылаться при использовании в этой функции.
В приведенном выше примере мы используем явную привязку, вызывая функцию thisGuard()
, таким образом используя метод apply()
, который принадлежит Function.prototype.
Что он делает, так это то, что он вызывает функцию и передает объект в качестве параметра. Внутри вызываемой функции this
будет привязан к объекту, который передается в качестве параметра.
Это эффективный способ гарантировать, что this
будет предсказуемо привязан к желаемому объекту.
Очень похожий метод call()
приводит к тому же поведению.
Основное различие между apply()
и call()
заключается в том, что если мы хотим передать функции дополнительные параметры, мы можем сделать это двумя разными способами:
С apply()
вторым параметром будет список дополнительных параметров, а с call()
дополнительные параметры перечислены, как обычно.
Другой случай явного связывания - использование метода bind()
, который был представлен в ES5.
Мы вернемся к этому примеру чуть позже.
Неявная привязка
Если вы не использовали ключевое слово new
при вызове функции и не применили явную привязку, в следующий раз следует обратить внимание на то, является ли this
неявно привязанным.
Неявное связывание происходит, когда функция, из которой ссылается this
, является методом объекта.
Давайте вернемся к самому первому примеру из этой статьи, но без setTimeout()
В этом случае identify()
- это метод объекта thisGuard
, и this
будет неявно привязан к этому объекту.
Стоит отметить, что явная привязка имеет приоритет над неявной привязкой и как жесткая привязка имеет приоритет над явной привязкой .
Привязка по умолчанию
Наконец, если ни один из вышеперечисленных случаев отсутствует, вы можете ожидать, что this
по умолчанию привяжется к глобальной области.
То есть вы можете ожидать ссылку на объект window
при ссылке на this
.
Если вы используете строгий режим, вы можете ожидать, что this
будет неопределенным.
Если мы вернемся к самому первому примеру из этой статьи в том виде, в котором он был изначально, вы сможете понять, почему он печатает undefined
вместо предполагаемых firstName
и rank
Когда мы передаем функцию в качестве аргумента setTimeout
, мы не вызываем функцию, вместо этого мы передаем ссылку.
Через одну секунду функция будет вызвана как функция обратного вызова, а выполнение context будет глобальной областью видимости, поэтому this
не будет неявно привязан к thisGuard
, как мы ожидали.
Зная, как this
работает в JavaScript, мы знаем, что можем легко решить эту проблему, жестко привязав this
к объекту thisGuard
.
Выражения стрелочной функции
Наконец, я хочу упомянуть об использовании выражений стрелочных функций.
В ES6 была введена стрелочная функция.
Одно из основных преимуществ использования выражений стрелочных функций по сравнению с регулярные функциональные выражения - это то, что this
изнутри стрелочной функции всегда будет указывать на включающий лексический контекст.
Давайте посмотрим на пример с регулярными функциональными выражениями
Здесь мы видим, как первый вызов regularFunction()
использует привязку по умолчанию, а второй вызов - неявную привязку.
В результате this
указывает на два разных контекста в зависимости от сайт вызова, как и следовало ожидать.
Если бы мы хотели убедиться, что this
будет лексически привязанным, а не динамически, мы могли бы вместо этого использовать стрелочную функцию.
Теперь сайт вызова функции больше не влияет на то, на что this
будет ссылаться при использовании в arrowFunction()
.
Заключение
Основная причина того, что значение this
сбивает нас с толку, заключается в том, что ссылки в JavaScript обычно следуют модели лексической области видимости, тогда как this
- это ссылка на контекст выполнения, из которого функция вызывается. Таким образом, this
определяется динамически и не соответствует общей концепции ссылок в JavaScript.
Однако мы всегда можем определить, на что указывает this
, выбрав 4 способа this
привязки.
В качестве альтернативы мы можем использовать стрелочные функции, чтобы убедиться, что this
не будет привязана динамически.
Для получения дополнительных знаний о JavaScript я предлагаю вам также просмотреть мою другую статью Мастер JavaScript: советы и хитрости
Вот и все!
Если у вас есть вопросы или отзывы, оставьте комментарий ниже. Если вам понравилась эта статья, нажмите кнопку хлопка 👏 пару раз!
Вы также можете найти меня в Твиттере, где я буду публиковать другие материалы, похожие на это.