Поднятие или hoisting - это механизм в JavaScript, в котором переменные и объявления функций, передвигаются вверх своей области видимости перед тем, как код будет выполнен.

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

Стоит отметить то, что механизм "поднятия" передвигает только объявления функции или переменной. Назначения переменным остаются на своих местах.


Undefined vs ReferenceError

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

console.log(typeof variable); // Выводит: undefined

Это приведет нас к первому заключению. В JavaScript, необъявленной переменной при выполнении кода назначается значение undefined, а так же и тип undefined.

Вторым заключением будет:

console.log(variable); // Выводит: ReferenceError: variable is not defined

В JavaScript, ReferenceError появляется при попытке доступа к предварительно необъявленной переменной.


"Поднятие" переменных

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

Однако, в JavaScript мы можем объявлять и инициализировать наши переменные одновременно, как в этом ну просто самом распространенном примере:

var a = 100;

Запомните и держите в уме одну важную деталь, JavaScript непреклонно сначала объявляет, а уже затем инициализирует наши переменные.

Как упоминалось ранее, все переменные и объявления функций поднимаются вверх своей области видимости. Мне также стоит добавить, что объявление переменных происходит перед выполнением кода.

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

Чтобы продемонстрировать это поведение, давайте посмотрим на следующий код.

function hoist() {
    a = 20;
    var b = 100;
}

hoist();

console.log(a);
/*
    Доступ как к глобальной переменной вне функции hoist()
    Выводит: 20
*/

console.log(b);
/*
    Как только b была назначена, она заключена в рамки области видимости функции hoist().
    Что означает то, что мы не можем вывести её за рамки функции.
    Вывод: ReferenceError: b is not defined
*/

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

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


Глобальные переменные

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

console.log(hoist); // Вывод: undefined

var hoist = 'The variable has been hoisted.';

Мы ожидали, что результат в логе будет: ReferenceError: hoist is not defined, но вместо этого нам вывело undefined.

Почему так произошло?

JavaScript "поднял" объявление переменной. Вот как это выглядит для движка JavaScript:

var hoist;

console.log(hoist); // Вывод: undefined
hoist = 'The variable has been hoisted.';

Поэтому мы можем использовать переменные перед тем как мы объявим их. Тем не менее, в этом вопросе нужно быть аккуратными, так как "поднятая" переменная инициализируется со значением undefined. Лучшим вариантом, как говорилось выше, было бы объявить и инициализировать нашу переменную перед её непосредственным использованием.


Переменные в области видимости функции

Как мы видели до этого, переменные в глобальной области видимости поднимаются вверх. Дальше давайте посмотрим на то, как ведут себя переменные при "поднятии" в функциях .

function hoist() {
    console.log(message);
    var message = 'Hoisting is all the rage!'
}

hoist();

Подумайте, что нам выдаст эта функция? Если вы предположили - undefined, то вы оказались правы. Вот как движок видит код выше:

function hoist() {
    var message;
    console.log(message);
    message = 'Hoisting is all the rage!'
}

hoist(); // Вывод: undefined

Объявление переменной var message, область видимости которой заканчивается в функции hoist(), "поднимается" наверх функции.


Strict mode

Спасибо такой полезной штуковине как strict mode в ES5 версии JavaScript, с помощью которой вы можете быть внимательнее при объявлении переменных. Включая strict mode, мы включаем некий ограниченный вариант JavaScript, который не будет возиться с использованием переменных перед их объявлением.

Запуск кода в strict mode:

  • Устраняет некоторые скрытые ошибки в JavaScript, изменяя их на явную выдачу ошибок, которые будут в последствии выданы движком.
  • Устраняет ошибки, которые затрудняют движкам JavaScript выполнять оптимизацию.
  • Запрещает некоторый синтаксис, который с большой долей вероятности будет уже идти из коробки в будущих версиях JavaScript.

Мы включаем "строгий режим", заранее указывая в нашем файле или функции следующее:

'use strict';

// или
"use strict";

Поднятие функций

JavaScript функции могут классифицироваться как объявленные функции, так и как функциональные выражения.

Объявленные функции

Такие функции полностью поднимаются вверх кода. Теперь понятно почему JavaScript позволяет нам вызывать функции прежде, чем мы их объявим по упоминанию в коде.

hoisted(); // Вывод: "This function has been hoisted."

function hoisted() {
    console.log('This function has been hoisted.');
};

Функциональные выражения, однако, не поднимаются.

expression(); //Вывод: "TypeError: expression is not a function

var expression = function() {
    console.log('Will this work?');
};

Как мы можем видеть выше, объявление переменной var expression поднимается, но его назначение как функции - нет. Следовательно, движок выдаст TypeError как увидит expression в виде переменой, а не функции.


Порядок по приоритетам

Очень важно помнить несколько вещей, объявляя JavaScript функции и переменные.

  • Назначение переменных имеет приоритет перед объявлением функции.
  • Объявление функции имеет приоритет перед объявлением переменной.

Объявления функций "поднимаются" над объявлением переменных, но не над их назначениями.

Назначение переменной над объявлением функции.

var double = 22;

function double(num) {
    return (num*2);
}

console.log(typeof double); // Вывод: number

Объявление функции над объявлением переменной.

var double;

function double(num) {
    return (num*2);
}

console.log(typeof double); // Вывод: function

Даже если мы изменим позиции объявлений, JavaScript движок все равно бы взял double функцию.