Мастер 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 действия:

  1. Создайте пустой простой объект JavaScript.
  2. Свяжите этот объект с другим объектом.
  3. Передайте вновь созданный объект из шага 1 как this контекст.
  4. Вернуть 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: советы и хитрости

Вот и все!
Если у вас есть вопросы или отзывы, оставьте комментарий ниже. Если вам понравилась эта статья, нажмите кнопку хлопка 👏 пару раз!
Вы также можете найти меня в Твиттере, где я буду публиковать другие материалы, похожие на это.