Что такое Hoisting в JavaScript

И как пользоваться этим “поднятием”

Возможно, вы уже знаете, что переменные могут “подниматься”. “Hoisting” переводится с английского как “поднятие” и означает понятие, которое было придумано для того, чтобы можно было говорить о замыканиях в JavaScript без указания области видимости переменных.

Перед тем как начать, следует ознакомиться с терминами из статьи, такими как лексическое окружение, обработчики синтаксиса и контексты выполнения.

Рассмотрим явление, которое происходит в JavaScript и зачастую путает людей, работающих с ним. Допустим, у нас есть переменная а и функция b().

Итак, что же мы ожидаем увидеть? Мы ожидаем увидеть надпись “function b called” (“функция b вызвана”), а затем “Namaste Human”.

Результат совпадает с ожиданиями

Теперь рассмотрим то, что, скорее всего, вообще не будет работать в других языках программирования. Вернемся к коду: передвинем вызов функции b() и вывод значения переменной а вверх, в начало кода.

В большинстве языков программирования такая запись выдаст ошибку, поскольку обычно они выполняют код строка за строкой. Так как функция b() еще не была объявлена перед вызовом, мы пока не можем ее использовать. По крайней мере такого поведения следует ожидать. Однако в JavaScript дела обстоят немного иначе.

Функция запустилась. Вместо того, чтобы выдать ошибку, она предоставляет значение, но не переменной а, а вызова функции. Переменная же представлена в виде undefined.

Функция вызвалась несмотря на то, что была объявлена ниже

На самом деле переменная а все еще доступна даже несмотря на то, что не представлена подходящим значением. Вот что произойдет, если удалить переменную а.

А что будет, если вызвать переменную а без объявления?

Консоль выдает ошибку a is not defined (переменная а не определена).

Как и следовало ожидать

Теперь помещаем переменную внутрь JS-файла.

Объявим переменную а где-нибудь после ее вызова
Вывод выдаст результат undefined

Такой феномен называется “поднятием” (hoisting).

Описания в интернете могут дать неверное представление об этом процессе. Как правило, в них говорится о том, что переменные и функции в JavaScript поднимаются в самый верх программы из-за движка JS, будто их на самом деле туда переместили, а поэтому они могут работать в любом месте.

Но теперь уже можно сказать, что это не совсем так, ведь переменная не была задана как Namaste Human. Поэтому объяснение, что строка передвигается в начало файла, не совсем корректно. Это скорее выглядит следующим образом.

В результате мы все равно получим undefined, как на фото ниже

Происходит так, будто мы объявили переменную, а значение будет присвоено ей позднее. Но это не то, что было написано. Дело в том, что весь код преобразуется движком JavaScript.

Вот код, который мы писали в начале.

Чтобы разобраться во внутренних процессах JavaScript, нужно копнуть немного глубже в контекст выполнения программы. Дело в том, что он запускается в два этапа. Это и есть причина, по которой переменные и функции JavaScript в некотором роде доступны, даже если были объявлены в коде позже.

Первая фаза называется фазой создания. Здесь у нас есть глобальный объект, который выделяет себе место в памяти. Затем у нас есть this, которое делает то же самое.

Следует помнить, что this создается внутри контекста выполнения программы. Затем создаётся внешнее окружение.

В фазе создания парсер проходит через весь код и начинает настраивать написанное для преобразования. Он распознает места, где мы создали переменные или функции, а затем выделяет пространство в памяти для всех этих данных. И именно этот процесс называют поднятием.

Но JavaScript не перемещает код вверх. На самом деле его движок выделяет место в памяти для всех переменных, лежащих в коде, еще до начала построчного выполнения программы.

Когда код начинает запускаться строка за строкой, он уже имеет доступ ко всем элементам. Однако в случае переменных все немного сложнее. Функции, будучи обработанными парсером, целиком помещаются в память. Вторая фаза (фаза выполнения, когда код выполняется построчно)  —  это как раз тот момент, когда настраиваются все присваивания, а переменная а получает какое-либо значение.

Поэтому движок JavaScript, начиная выделять место в памяти под переменную, еще не знает, какое значение она примет. Поэтому она может быть чем угодно: объектом, функцией, массивом, строкой или даже символом. Пока движок не начнет выполнять код, он заполняет переменную значением undefined. Заполнитель undefined используется для тех переменных, значение которых не было определено.

Все переменные в JavaScript по умолчанию заданы ключевым словом undefined

Все эти процессы происходят, потому что где-то в лексическом окружении языка происходит нечто, представленное ниже.

Вот что происходит “под капотом” у JavaScript

Это значит, что опираться на поднятие переменных и функций  —  не лучшая идея, оно может доставить кучу проблем.

Вот как делать не нужно.

Не вызывайте переменные и функции до того, как объявите их

Вместо этого лучше сделать так.

Сначала объявите переменные, а затем вызывайте их

Теперь мы уже понимаем, что значит поднятие. Мы и вправду можем вызвать функцию несмотря на то, что она объявлена позже. Это связано с тем, что написанный код не выполняется напрямую. Движок JS обрабатывает его и лишь затем принимает решения. Это немного странно, но так он работает.

Сравнение var, let и const при поднятии

Процессы, показанные ниже, аналогичны как для let, так и для const.

Переменная а объявлена ключевым словом let

Как вы думаете, каким будет результат вывода программы?

После запуска мы получаем ошибку ReferenceError.

Эта ошибка всплывает из-за Временной мертвой зоны (Temporal Dead Zone), однако не стоит пугаться этого термина. Он обозначает период между созданием переменной и её инициализацией, когда мы не можем получить к ней доступ.

Значит ли это, что все переменные, объявленные с помощью let и const не “поднимаются” в коде? Нет, они тоже поднимаются.

Однако в случае с var переменные объявлялись и получали значение undefined, а с letи constони просто не получают это ключевое слово.

Выводы

  • Написанный код не выполняется напрямую, потому что движок JavaScript обрабатывает код и принимает решения на его основе.
  • Мы можем вызывать функции, даже если они были объявлены в коде позже, но лучше не делать так с переменными.
  • Переменные и функции в JavaScript “поднимаются” в верх кода. Однако это не значит, что они и вправду меняют свое положение внутри программы.
  • Переменные, объявленные с помощью var, поднимаются и принимают значение undefined.
  • Объявления функций поднимаются и инициализируются вместе с ссылкой на функцию, то есть находятся в памяти целиком.
  • Переменные, объявленные с помощью let и const тоже поднимаются, но мы не можем получить к ним доступ до их объявления из-за так называемой Временной мертвой зоны.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Somnath Singh: 90% of Developers get this wrong

Предыдущая статья5 эффективных Unix-команд для устранения неполадок
Следующая статья6 ответов на вопрос: «почему читать код важнее, чем писать?»