В JavaScript есть уникальная особенность, переменную или функцию можно использовать по коду выше, её объявление:
greeting()
function greeting() {
console.log('Привет Васятка!')
}И это не вызовет ошибок! Такое необычное поведения языка, обусловлено механизмом всплытия (бурж: hoisting), который мы подробно разберём в данной статье.
Механика всплытия
Всплытие работает так: на стадии компиляции кода, все переменные и функции регистрируются в начале своих областей видимости. Всплывают они до ближайшей функции, вмещающей их. А если таковая отсутствует, то до глобальной области видимости, становясь свойствами и методами объекта window:
window.greeting()
function greeting() {
console.log('Привет Васятка!')
}Переменные объявленные через let и const тоже всплывают, но только до блочной области видимости.
Всплытие основано на том что движок V8, на этапе лексического разбора кода, происходящего перед запуском программы, меняет её структуру. Переменные и функции перемещаются (регистрируются) вверх областей видимости. И уже результаты этого разбора используются при выполнении скрипта. Порядок всплытия следующий:
Функции;
Переменные.
По факту в движке V8 нет такого механизма как всплытие. Просто это удобная метафора, описывающая ряд аспектов обработки объявления переменных и функций, на фазе компиляции.
Всплытие функций
Всплывают только формально объявленные функции, но не функциональные выражения присвоенные переменной:
greeting() // Uncaught TypeError: greeting is not a function
var greeting = function() {
console.log('Привет Васятка!')
}Получаемая ошибка TypeError, говорит о попытке сделать что-то недопустимое со значением. В зависимости от среды выполнения, можно получить ошибки: Undefinde is not a function или greeting is not a function. Обратите внимание что JavaScript не выдал ошибку ReferenceError, говорящую о том что такой идентификатор не найден. Ошибка TypeError говорит что такой идентификатор был найден, но на текущий момент не содержит ссылки на функцию и значением переменной является undefined, который Вы пытаетесь использовать как функцию. Значение undefined автоматически присваивается любой переменной объявленной при помощи var и фактически выполняемый код выглядит так:
var greeting = undefined // Всплытие
greeting() // Пытаемся вызвать undefined как функцию, что приводит к ошибке
greeting = function() { // Присваивание происходит только тут
console.log('Привет Васятка!')
}Такое значение у переменной будет до момента пока интерпретатор не дойдёт до строчки с присваиванием. То есть произошло всплытие переменной greeting, но не функционального выражения.
TDZ
Если в коде поменять объявление переменной с var на let мы словим другую ошибку:
greeting() // Uncaught ReferenceError: Cannot access 'greeting' before initialization
let greeting = function() {
console.log('Привет Васятка!')
}Разница ошибок возникает потому что у переменных объявленных через let и const есть механизм Temporal Dead Zone (TDZ), не дающий обратиться к переменной до её фактического объявления в коде. Это не значит что всплытие для let и const не работает, как многие считают. Оно просто работает иначе, если var всплывает и по умолчанию получает значение undefined, то let и const тоже всплывают, но без инициализации. Собственно об этом и говорит ошибка ReferenceError, движок JavaScript знает о существовании переменной greeting, так как она уже всплыла, но доступ к ней заблокирован, вследствие чего возникает TDZ.
Если коротко резюмировать данный раздел, то используете классическое объявление функций (бурж: Function Declaration), прибегая к функциональным выражениям (бурж: Function Expression), только там где это реально необходимо. В большинстве случаев функциональные выражения не дают преимуществ, но могут привести к сложнодиагностируемым багам, вызванным отличиями работы всплытия, у разных форм объявления переменных.
Всплытие и читаемость кода
При помощи всплытия функций, можно повысить читаемость кода, размести вызовы функций вначале файла. Это позволит разработчику с ходу оценить что делает скрипт, обращаясь за подробностями, в тело функций только по необходимости.
А вот с переменными ситуация обратная. Использование переменной до её фактического объявления, наоборот ухудшает читаемость кода.
Вывод
Всплытие кажется чрезвычайно простым механизмом JavaScript, но на деле он обладает рядом неочевидных нюансов, которые могу вызвать проблемы. В данной, небольшой статье я постарался детально описать суть этого явления. Надеюсь она поможет кому-то избежать багов или пофлэксить подробным описанием работы всплытия на собесе.
