Hoisting
JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables, classes, or imports to the top of their scope, prior to execution of the code.
Hoisting is not a term normatively defined in the ECMAScript specification. The spec does define a group of declarations as HoistableDeclaration, but this only includes function , function* , async function , and async function* declarations. Hoisting is often considered a feature of var declarations as well, although in a different way. In colloquial terms, any of the following behaviors may be regarded as hoisting:
- Being able to use a variable’s value in its scope before the line it is declared. («Value hoisting»)
- Being able to reference a variable in its scope before the line it is declared, without throwing a ReferenceError , but the value is always undefined . («Declaration hoisting»)
- The declaration of the variable causes behavior changes in its scope before the line in which it is declared.
- The side effects of a declaration are produced before evaluating the rest of the code that contains it.
The four function declarations above are hoisted with type 1 behavior; var declaration is hoisted with type 2 behavior; let , const , and class declarations (also collectively called lexical declarations) are hoisted with type 3 behavior; import declarations are hoisted with type 1 and type 4 behavior.
Some prefer to see let , const , and class as non-hoisting, because the temporal dead zone strictly forbids any use of the variable before its declaration. This dissent is fine, since hoisting is not a universally-agreed term. However, the temporal dead zone can cause other observable changes in its scope, which suggests there’s some form of hoisting:
const x = 1; console.log(x); // ReferenceError const x = 2; >
If the const x = 2 declaration is not hoisted at all (as in, it only comes into effect when it’s executed), then the console.log(x) statement should be able to read the x value from the upper scope. However, because the const declaration still «taints» the entire scope it’s defined in, the console.log(x) statement reads the x from the const x = 2 declaration instead, which is not yet initialized, and throws a ReferenceError . Still, it may be more useful to characterize lexical declarations as non-hoisting, because from a utilitarian perspective, the hoisting of these declarations doesn’t bring any meaningful features.
Note that the following is not a form of hoisting:
var x = 1; > console.log(x); // 1
There’s no «access before declaration» here; it’s simply because var declarations are not scoped to blocks.
For more information on hoisting, see:
- var / let / const hoisting — Grammar and types guide
- function hoisting — Functions guide
- class hoisting — Classes guide
- import hoisting — JavaScript modules
See also
- var statement — MDN
- let statement — MDN
- const statement — MDN
- function statement — MDN
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on Aug 4, 2023 by MDN contributors.
Your blueprint for a better internet.
Объяснение Hoisting в JavaScript с помощью визуализации
Это перевод статьи Лидии Холли, где она разбирает такое понятие как Hoisting.
Hoisting — это один из тех терминов, о котором слышал каждый разработчик JS, потому что вы наверняка гуглили ошибку и попали на StackOverflow, где вам объяснили, что эта ошибка была связана с «hoisting» Итак, что такое hoisting?
Если вы начинающий в разработке на JavaScript, возможно, вы столкнулись с «странным» поведением, когда некоторые переменные undefined , возникают ошибки ReferenceErrors и т.д. Hoisting часто объясняется помещением переменных и функций в начало файла, но нет, это не то, что происходит на самом деле, давайте разбираться почему так.
Когда JS-движок получает наш скрипт, первое, что он делает, — это сетапит память для данных в нашем коде. На этом этапе код не выполняется, просто все готовится к выполнению. Способ хранения объявлений функций и переменных отличается. Функции хранятся со ссылкой на всю функцию:
С переменными все немного иначе. ES6 представил два новых ключевых слова для объявления переменных: let и const . Переменные, объявленные с помощью ключевого слова let или const , сохраняются неинициализированными:
Переменные, объявленные с помощью ключевого слова var , сохраняются со значением по умолчанию undefined .
Теперь, когда этап создания завершен, мы можем фактически выполнить код. Давайте посмотрим, что произойдет, если у нас есть 3 оператора console.log вверху файла до того, как мы объявим функцию или любую из переменных.
Поскольку функции хранятся со ссылкой на весь код функции, мы можем вызывать их даже перед строкой, в которой мы их создали.
Когда мы ссылаемся на переменную, объявленную с ключевым словом var перед их объявлением, она просто возвращает значение по умолчанию, с которым она была сохранена: undefined . Однако иногда это могло привести к «неожиданному» поведению. В большинстве случаев это означает, что вы ссылаетесь на него непреднамеренно (вы, вероятно, не хотите, чтобы он действительно имел значение undefined )
Чтобы предотвратить возможность случайной ссылки на неопределенную переменную, как мы могли бы с ключевым словом var, при каждой попытке доступа к неинициализированным переменным выдается ошибка ReferenceError. «Зона» перед их фактическим объявлением называется временной мертвой зоной: вы не можете ссылаться на переменные (включая классы ES6!) До их инициализации.
Когда движок передает строку, в которой мы фактически объявили переменные, значения в памяти перезаписываются значениями, с которыми мы их фактически объявили.
Резюме: функции и переменные хранятся в памяти execution context’a до того, как мы выполним наш код. Это называется Hoisting.
Что в javascript подвергается всплытию hoisting
Hoisting представляет процесс доступа к переменным до их определения. Возможно, данная концепция выглядит немного странно, но она связана с работой компилятора JavaScript. Компиляция кода происходит в два прохода. При первом проходе компилятор получает все объявления переменных, все идентификаторы. При этом никакой код не выполняется, методы не вызываются. При втором проходе собственно происходит выполнение. И даже если переменная определена после непосредственного использования, ошибки не возникнет, так как при первом проходе компилятору уже известны все переменные.
То есть как будто происходит поднятие кода с определением переменных и функций вверх до их непосредственного использования. Поднятие на английский переводится как hoisting, сообственно поэтому данный процесс так и называется.
Переменные var
var-переменные, которые попадают под hoisting, по умолчанию получают значение undefined. Например, возьмем следующий простейший код:
console.log(foo);
Его выполнение вызовет ошибку ReferenceError: foo is not defined
Добавим определение переменной:
console.log(foo); // undefined var foo = "Tom";
В этом случае консоль выведет значение «undefined». При первом проходе компилятор узнает про существование переменной foo. Она получает значение undefined. При втором проходе вызывается метод console.log(foo) .
Возьмем другой пример:
var c = a * b; var a = 7; var b = 3; console.log(c); // NaN
Здесь та же ситуация. Переменные a и b используются до опеределения. По умолчанию им присваиваются значения undefined. А если умножить undefined на undefined, то получим Not a Number (NaN).
Все то же самое относится и к использованию функций. Мы можем сначала вызвать функцию, а потом уже ее определить:
display(); function display()
Здесь функция display благополучно отработает, несмотря на то, что она определена после вызова.
Но от этой ситуации надо отличать тот случай, когда функция определяется в виде переменной:
display(); var display = function ()
В данном случае мы получим ошибку TypeError: display is not a function . При первом проходе компилятор также получит переменную display и присвоет ей значение undefined. При втором проходе, когда надо будет вызывать функцию, на которую будет ссылаться эта переменная, компилятор увидит, что вызывать то нечего: переменная display пока еще равна undefined. И будет выброшена ошибка.
let-переменные и константы
Процесс hoisting для let-переменных и констант будет отличаться: в отличие от var-переменных константам и let-переменным при хостинге не присваивается начальное значение . Итак, выше мы видели, что если мы используем var-переменные до объявления, то они получают по умолчанию значение undefined . Теперь посмотрим, что будет с let-переменной:
console.log(foo); // Uncaught ReferenceError: Cannot access 'foo' before initialization let foo = "Tom"; console.log(foo); // не будет выполняться
В данном случае в первой строке мы получим ошибку
Uncaught ReferenceError: Cannot access 'foo' before initialization
От этой ситуации следует отличать момент, когда let -переменная объявлена, но не инициализирована:
let foo; // по умолчанию foo = undefined console.log(foo); // undefined foo = "Tom"; console.log(foo); // Tom
Неинициализированная let -переменная по умолчанию будет иметь значение undefined , и в данном случае ошибки не будет.
То же самое касается и констант:
console.log(foo); // Uncaught ReferenceError: Cannot access 'foo' before initialization const foo = "Tom"; console.log(foo); // не будет выполняться
Поднятие или hoisting в JavaScript
Что такое «поднятие» и как оно работает в JavaScript.
JavaScript · 10.12.2019 · читать 4 мин · Автор: Alexey Myzgin
- Поднятие (hoisting)
- Поднятие функций
- Поднятие const, let и var
- Ключевое слово var
- Ключевые слова const / let
Поднятие предполагает, что объявления переменных var и функций function физически перемещаются в начало кода, но, на самом деле это не так.
По сути, когда Javascript компилирует весь код, все объявления переменных, использующие var , поднимаются/hoisted в верхнюю часть их функциональной/локальной области видимости (если объявляется внутри функции) или в глобальную область видимости (если объявляется вне функции) независимо от того, где была сделана фактическая декларация.
Объявления переменных и функций помещаются в память на этапе компиляции, но они остаются именно там, где мы ввели их в свой код.
Итак, под капотом происходит следующее: на этапе создания, движок JavaScript просматривает код и, как только он видит ключевое слово var или ключевое слово function , он выделяет некоторую память для них.
Одним из преимуществ JavaScript является помещение объявления функций в память, перед выполнением любого сегмента кода. Объявления функций поднимаются, но они идут на самый верх, поэтому они будут находиться над всеми объявлениями переменных. Это позволяет нам использовать функцию до того, как мы объявим её в своем коде. Например:
function printName(name) console.log("Hello, my name is " + name); > printName("Alex");Теперь, давайте посмотрим, что произойдёт, когда мы вызовем функцию printName , прежде чем напишем её:
printName("Alex"); function printName(name) console.log("Hello, my name is " + name); >Несмотря на то, что мы вызвали функцию printName , перед тем, как написать её, код всё ещё работает. Это происходит из-за того, как контекстное выполнение работает в JavaScript.
Поднятие хорошо сотрудничает с другими типами данных. Переменные можно инициализировать и использовать до их объявления.
JavaScript поднимает только объявления, а не инициализации. Если переменная объявлена и инициализирована после её использования, значение будет неопределенным (undefined). Например:
console.log(name); // undefined var name; name = "Alex";Если мы объявляем переменную после её использования, но предварительно инициализируем её, она вернет значение:
name = "Alex"; console.log(name); // Alex var name;Таким образом переменные частично подняты. Поднимая переменную, но не правую сторону (не фактическое значение), мы просто присваиваем ему undefined .
Функции полностью подняты. Означает, что объявлению функции, на этапе создания, было назначено место в памяти.
Поднятие const, let и var
var — это традиционный способ объявления переменных в JavaScript.
ES6 (ECMAScript 6) представил два новых способа объявления переменных: const и let , и, как правило, они рекомендуются во избежание неожиданных осложнений при подъеме.
Ключевое слово var
- var имеет область действия функции;
- объявления var поднимаются, но не инициализируются.
console.log(name); // undefined var name = "Alex";Приведенный выше код, из-за поднятия эквивалентен приведенному коду ниже.
var name; console.log(name); // undefined name = "Alex";Ключевые слова const / let
- const и let имеют область видимости блока.
На самом деле объявления var , let , const , function и class поднимаются; но, мы должны помнить, что концепция поднятия не является буквальным процессом (т. е. сами объявления не перемещаются в начало файла — это просто процесс компилятора JavaScript, который сначала читает их, чтобы освободить для них место в памяти).
Разница между объявлениями var / function и объявлениями let / const / class заключается в инициализации. Первые инициализируются с неопределенным значением undefined . Однако, вторые, лексически объявленные переменные, остаются не инициализированными. Это означает, что ReferenceError выбрасывается при попытке доступа к ним. Они будут инициализированы только после того, как операторы let / const / class будут определены. Всё что до, называется временной мертвой зоной.
Временная мертвая зона — это не синтаксическое местоположение, а время между созданием переменной (области) и инициализацией. Ссылка на переменную в коде над объявлением не является ошибкой, если этот код не выполняется (например, тело функции или просто мертвый код), но ошибка будет выдана, если мы запросим доступ к переменной до её инициализации.
Разница между объявлениями var , let и const заключается в их инициализации.
Экземпляры var и let могут быть инициализированы без значения, в то время как const выдаст ошибку ReferenceError , если ты попытаешься объявить её без одновременного присвоения ей значения. Так что const myName = ‘Alex’ будет работать, но const myName; myName = ‘Alex’; не будет. С помощью var и let ты можешь попробовать использовать значение var до того, как оно будет присвоено, и оно вернет undefined . Однако, если ты сделаешь то же самое с let — получишь ReferenceError .
console.log(a); // undefined var a = 1; console.log(b); // ReferenceError: Cannot access 'b' before initialization let b = 2; console.log(c); // ReferenceError: Cannot access 'c' before initialization const c = 5;Также, если ты создашь var на верхнем уровне (глобальный уровень), создастся свойство для глобального объекта; в случае с браузером — это объект window . Поэтому на создание var myName = ‘Alex’; можно ссылаться также путем вызова window.myName .
Однако, если ты напишешь let newName = ‘Alex’; это не будет доступно в глобальном объекте window — следовательно, ты не сможешь использовать window.newName в качестве ссылки на ‘Alex’ ,
К объявлениям, сделанным с помощью var , можно получить доступ за пределами их первоначальной области видимости, тогда как к объявлениям, сделанным с помощью let и const , нельзя.
console.log('1 myName1', myName1); // undefined if (1) console.log('2 myName1', myName1); // undefined var myName1 = 'Alex'; > console.log('1 myName2', myName2); // ReferenceError: myName2 is not defined if (1) console.log('1 myName2', myName2); // ReferenceError: Cannot access 'myName2' before initialization let myName2 = 'Alex'; > console.log('1 myName3', myName3); // ReferenceError: myName3 is not defined if (1) console.log('2 myName3', myName3); // ReferenceError: Cannot access 'myName3' before initialization const myName3 = 'Alex'; >
