Поднятие (hoisting)
Поднятие предполагает, что объявления переменных 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';
}