Поднятие(hoisting)

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

Что такое hoisting

Поднятие(hoisting) - Так называют некий механизм который относится только к объявлению функций и переменных. Если вы читали предыдущий урок, то вы уже знаете, что такое hoisting, просто вам об этом никто не сказал). Мы уже знаем, что такое фаза создания, так вот hoisting это как раз о том, когда переменные помещаются в лексическое окружение. То есть объявления переменных и функций попадают в память в процессе компиляции.

Отступление про функции

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

function a () {
    return 'a'
}

console.log(a); // тут мы получим тело функции

Или очень понятый пример:

function a () {
    return 'a';
}

a = undefined;
console.log(a); // undefined

Мы перезаписали функцию a и теперь там undefined. Все как и с обычной переменной.

Hoisting и функции

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

// мы вызвали функцию здесь
console.log(gintama("Кагура")); // Мой любимый персонаж в гинтаме это Кагура

// а здесь мы функцию создали
function gintama(name) {
  console.log(`Мой любимый персонаж в гинтаме это ${name}`);
}

Вы все правильно подумали! Потому что во время фазы создания наша функция поместилась в глобальное лексическое окружение. И во время выполнения когда мы наткнулись на вызов функции, мы ее выполняем, так как она есть в нашем лексическом окружении. Так вот то, что мы можем так сделать и называют hoisting.

  • Но это не работает с функциональными выражениями и с использованием стрелочных функций. Почему?

Все очень просто:

hi(); // ReferenceError: Cannot access 'hi' before initialization

let hi = function() {
    console.log('Привет красавчик:з');
};

Знакомая ошибка не правда ли? И вы снова все правильно подумали, молодец! Ну конечно же все потому, что тело функции мы присваиваем переменной и происходит это уже во время выполнения. Поэтому в примере выше, наше функциональное выражение ведет себя точно так же как и обычная переменная let.
Конечно же тоже касается и стрелочной функции.

hi(); // ReferenceError: Cannot access 'hi' before initialization

let hi = () => {
    console.log('Привет красавчик:з');
};

Мы можем поменять их на var.

hi(); // TypeError: hi is not a function

var hi = () => {
    console.log('Привет красавчик:з');
};

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

Примеры с переменными

Разберем пару примеров с разными переменными, лишним не будет.

Переменные var

Пример 1
// Здесь уже существует переменная var name.
console.log(name); // Здесь будет undefined, оно подставилось во время фазы создания.

var name = 'Dima' // присвоим нашу строку здесь

console.log(name); // Dima - здесь мы уже видим нашу строку
Пример 2
var x = 1; // Инициализируем x
console.log(x + " " + y);  // '1 undefined'
var y = 2;

Тоже самое что и:

var x;
var y;
x = 1; 
console.log(x + " " + y);  // '1 undefined'
y = 2;
Пример 3

Распространенный пример с функцией:

var a = 'Привет'; 

function b() {
    // вот здесь условие не сработает, но переменные var все равно будет в лексическом окружении функции
    if(false) { 
        var a = 'Дима'; // это условие не сработает, поэтому в Лекс Окр переменная a останется с undefined
    } else {
        var b = 'Сашенька ювелир'; // это условие сработает и присваивание произойдет
    }
    
    console.log(a); // undefined
    console.log(b); // Сашенька ювелир
}

b();

Код выше идентичен этому коду. Тут более понятно, почему это называют поднятием.

var a = 'Привет'; 

function b() {
    var a; // поэтому это и называется поднятие
    var b; // наши переменные будто бы поднялись на самый верх
    if(false) { 
        a = 'Дима'; 
    } else {
        b = 'Сашенька ювелир';
    }
    
    console.log(a); // undefined
    console.log(b); // Сашенька ювелир
}

b();

Переменные let

С переменной let все намного логичнее:

Пример 1
let x = 1; 
console.log(x + " " + y);
let y = 2;
// будет ошибка

С переменной let нам нужно обязательно сначала ее объявить, а уже потом использовать. Вспоминаем про TDZ.

let x; // любой пустой переменной присваивается undefined
console.log(x); // undefined

x = 1;
console.log(x); // 1

Пример 2
Пример с функцией как и с var:

let a = 'Привет'; 

function b() {
    if(false) { 
        // let не может уйти за рамки блока
        let a = 'Дима';  
    } else {
        let b = 'Сашенька ювелир'; 
    }
    
    // ищет переменную в родительском области видимости это переменная a из глобальной области
    console.log(a); // Привет
    //  ищет переменную выше, и находит нашу функцию b
    console.log(b); //  здесь будет сама функция b
}

b();

А вот что за поиск такой в родительской области видимости и что это за области такие мы уже узнаем в следующем уроке.

Обновлено 20.03.2023