Hoisting представляет процесс доступа к переменным до их определения. Возможно, данная концепция выглядит немного странно, но она связана с работой компилятора JavaScript. Компиляция кода происходит в два прохода. При первом проходе компилятор получает все объявления переменных, все идентификаторы. При этом никакой код не выполняется, методы не вызываются. При втором проходе собственно происходит выполнение. И даже если переменная определена после непосредственного использования, ошибки не возникнет, так как при первом проходе компилятору уже известны все переменные.
То есть как будто происходит поднятие кода с определением переменных и функций вверх до их непосредственного использования. Поднятие на английский переводится как hoisting, сообственно поэтому данный процесс так и называется.
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(){ console.log("Hello Hoisting"); }
Здесь функция display благополучно отработает, несмотря на то, что она определена после вызова.
Но от этой ситуации надо отличать тот случай, когда функция определяется в виде переменной:
display(); var display = function (){ console.log("Hello Hoisting"); }
В данном случае мы получим ошибку TypeError: display is not a function
. При первом проходе компилятор также получит переменную display и присвоет ей значение undefined.
При втором проходе, когда надо будет вызывать функцию, на которую будет ссылаться эта переменная, компилятор увидит, что вызывать то нечего: переменная
display пока еще равна undefined. И будет выброшена ошибка.
Процесс 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); // не будет выполняться