Укажите что в javascript подвергается всплытию hoisting
Поднятие переменных в Javascript
«Поднятие» (hoisting) – это поведение JavaScript по умолчанию, когда любая декларация передвигается вверх области видимости.
Декларации в JavaScript поднимаются
В JavaScript переменная может декларироваться уже после использования.
Другими словами, переменная может использоваться до того, как она будет декларирована.
Ниже, результат в примере 1 будет таким же, что в примере 2.
Пример 1
Пример 2
Чтобы в этом разобраться, необходимо понять сам термин «поднятие».
«Поднятие» (hoisting) – это поведение JavaScript по умолчанию, когда все декларации передвигаются вверх текущей области видимости (вверх текущего скрипта или текущей функции).
Инициализации не поднимаются
В JavaScript поднимаются только декларации, а не инициализации.
Ниже, результат в примере 1 будет не таким же, что в примере 2.
Пример 1
Пример 2
В последнем примере будет выведено undefined как значение переменной y. Это вызвано тем, что только декларации (var y), а не инициализации (=7) поднимаются вверх.
Благодаря «подъему» переменная y была декларирована перед использованием, но так как инициализация не поднимается, значение переменной y оказывается неопределенным (undefined).
Пример 2 аналогичен следующей записи:
Всегда декларируйте переменные в начале!
Для многих разработчиков «подъем» остается неизвестным или упускаемым из виду поведением JavaScript.
Если разработчик не учитывает «подъем», то в коде могут появляться ошибки.
Чтобы избежать подобных ошибок, всегда декларируйте все переменные в начале содержащей их области видимости. Учитывая то как JavaScript интерпретирует код, это будет хорошим правилом.
Внимание! В строгом режиме JavaScript не допускает использование переменных, если они не декларированы.
Область Видимости в JavaScript
Russian (Pусский) translation by Anton L (you can also view the original English article)
Понимание, как же движок JavaScript «думает» о области видимости предостережёт вас от известных ошибок, которые вызывает всплытие (hoisting), подготовит вас к работе с замыканиями (closures), и вероятнее всего вы не сделаете этих ошибок, после прочтения данной статьи.
. В любом случае, статья поможет вам понять всплытие и замыкания.
В ней мы рассмотрим:
Если вы хотите узнать больше о ES6 и как применять новый синтаксис и особенности спецификации, для улучшения и упрощения своего JavaScript кода, обратите внимание на следующие два курса:
Лексическая Область Видимости
Существует три способа задать область видимости в JavaScript:
Мы поговорим о каждом из этих механизмов в подробных деталях. Давайте узнаем, как JavaScript определяет какая переменная, какой области видимости принадлежит.
Процесс Компиляции: С Высоты Птичьего Полёта
Когда вы запускаете JavaScript код, происходят две вещи, которые заставляют его работать.
Во время компиляции, JavaScript движок:
Шаг 1: Компиляция
Давайте посмотрим, что делает компилятор.
Так как внутри функции больше нет объявлений переменных, компилятор вернётся в глобальную область видимости. Там нет объявлений переменных, на этом фаза компиляции закончится.
К этому момента наша программа знает:
Не важно если мы задали значения этим переменным в другом месте нашего кода. Движок JavaScript позаботится об этом во время выполнения.
Шаг 2: Выполнение
Во время следующей фазы, движок будет читать наш кода снова, на этот раз, выполняя его.
Оказывается под капотом JavaScript происходит гораздо больше, чем мы думали!
Теперь мы поняли, как JavaScript читает и выполняет, написанный вами код и мы готовы разобраться в том, как работает всплытие.
Всплытие под Микроскопом
Давайте начнём со следующего кода.
Запустив этот код можно заметить следующее:
Всплытие означает тот факт, что JavaScript делает все наши объявленные имена переменных доступными везде, в их области видимости до того, как мы присвоим им значение.
Всплытие Объявления Переменной
Вместо этого мы видим значение undefined и движок пытается использовать его, в том случает если вы попросите его об этом. Обычно это вызывает ошибку.
Зная об этом, мы можем предстать, что JavaScript видит в нашей функции bar следующую картину:
Это Первое Правило Всплытия: Переменные доступны в их области видимости, но имеют значение undefined пока в коде им не будет присвоено значение.
Если подумать об этом, это имеет смысл. Сразу становится понятно почему bar ведёт себя так, когда мы пишем код также, как движок JavaScript читает его, неправд-ли? Так почему же не писать его так всегда?
Всплытие Функционального Выражения (Function Expression)
Тот факт, что мы видим TypeError ошибку когда пытаемся запустить broken до того, как она была определена, тоже относится к первому правилу всплытия.
Всплытие Объявления Функции (Function Declaration)
Ну и наконец, зная это, мы можем вызвать bar до того, как мы определили данную функцию. Это связано со Вторым Правилом Всплытия: когда JavaScript компилятор находит объявление функции, имя и определение функции доступно сверху области видимости, которой она принадлежит. Давайте снова перепишем наш код:
И опять же, не кажется-ли вам, что проще писать JavaScript код, так как читает его движок?
По крайней мере, не совсем так.
. А также переменная убьёт вашу программу если вы попытаетесь использовать её ранее.
Тем самым у вас всегда будут гарантии, что у const будет значение, которое вы изначально присвоили.
Область Видимости Блока
У let и const есть и другое отличие от var : размер области видимости.
Если вы объявляете переменную через const или let внутри блока, она будет видна только внутри этого блока и только после того, как вы присвоите значение.
Лексический this и стрелочная функция
По крайней мере не всегда. В JavaScript не важно где вы, в какой области видимости, использовали слово this :
Вместо этого создаётся новый this внутри каждой функции, которую вы определяете, движок рассматривает значение this в зависимости не от того где вы определили функцию, а от того как вы её вызвали.
Похожий случай когда вы переопределяете любую переменную во внутренней области видимости:
Обычно, заставить this работать подобно лексической области видимости переменных, требует некоторых обходных путей:
В отличии от обычной функции, стрелочная функция не скрывает значение this в родительской области видимости, устанавливая при этом новое значение. Вместо этого данные функции лексически привязаны к значению this.
Другими словами, используя this в стрелочной функции, JavaScript ищет значение наверху, как и в случае с обычными переменными.
В начале значение this ищется в локальной области видимости. Так как стрелочная функция не устанавливает значение this, там найдено ничего не будет. После этого значение this будет искаться в родительской области видимости. Если значение найдётся там, то оно и будет использоваться.
Тем самым мы можем переписать код выше:
Больше подробностей о стрелочных функциях рассказывает инструктор Envato Tuts+ Dan Wellman в курсе Основы JavaScript ES6, а также советую взглянуть на документацию MDN по стрелочным функциям.
Заключение
Мы разобрали многое c момента начала этой статьи! Мы узнали:
Мы также рассмотрели два правила всплытия:
Полученные знания можно применить на практике при изучении замыканий JavaScript, в качестве следующего шага. Для этого советую вам ознакомиться со статьёй Область видимости и Замыкания Kyle Simpson’а.
Со всеми новыми знаниями, начинайте писать код и конечно же ошибки!
Область видимости в JavaScript и «поднятие» переменных и объявлений функций
Вы знаете, какое значение выведет этот код на JavaScript?
Если вас удивляет, что выведется «10», то следующий код вас и вовсе запутает:
В этом случае браузер выведет «1». Так что, собственно, происходит? Хотя такое поведение кажется странным, опасным и сбивающим с толку, на самом деле это очень мощное и выразительное средство JavaScript. Я не знаю, есть ли официальное название для такого поведения, но мне нравится использовать термин «поднятие»(«hoisting»). В этой статье я попытаюсь пролить свет на этот механизм языка, но сначала давайте поговорим об области видимости в JavaScript.
Область видимости в JavaScript
Одна из причин, приводящих в замешательство новичков, — это область видимости. Вообще, не только новичков. Я встречал много опытных JavaScript-разработчиков, которые не понимают механизм области видимости в JavaScript. Причина в том, что внешне JavaScript очень похож на любой другой Си-подобный язык.
Давайте рассмотрим следующий код на Cи:
Эта программа выведет 1, 2, 1, потому что Си и все остальные Си-подобные языки реализуют области видимости на уровне блоков кода. Когда исполняется новый блок кода, например условие if, новые переменные, объявленные в нём, не повлияют на переменные внешней области видимости.
Но не в случае JavaScript. Попробуйте запустить вот этот код в Firebug:
На этот раз будут выведены числа 1, 2, 2. Это связано с тем, что в JavaScript используется область видимости на уровне функций. Это совсем не то, что мы привыкли видеть в языках программирования, вроде Си. Блоки кода, вроде того, который у нас идёт сразу после if, не создают новую область видимости. Только функции создают новые области видимости.
Для многих программистов, привыкших к Си, C++, C# или Java такое поведение очень неожиданное и неприятное. К счастью, благодаря гибкости функций JavaScript, можно обойти эту проблему. Чтобы создать временную область видимости внутри функции, достаточно сделать следующее:
Такой подход достаточно гибок и может быть использован везде, где вам нужна временная область видимости, не только внутри блоков кода. Но я настаиваю на том, чтобы вы всё-таки потратили своё время, чтобы понять реализацию области видимости в JavaScript. Это довольно мощная особенность языка, которая мне очень нравится. Если вы понимаете область видимости, вам проще будет разобраться в «поднятии» переменных и объявлений функций.
Объявления, именование и «поднятие» переменных и функций
на самом деле интерпретируется так:
Оказывается, не важно, будет ли вообще выполнена строка, в которой происходит объявление. Следующие две функции эквивалентны:
Обратите внимание, что присваивание значений переменным не поднимается вместе с их объявлением. Поднимаются только объявления переменных. В случае с функциями, поднимается вся функция целиком. Существуют два основных способа объявить функцию, давайте их рассмотрим:
в этом случае поднимается только функция bar. Идентификатор «foo» также поднимается, но не анонимная функция — она остаётся на месте.
Вот мы и описали основные моменты «поднятия» переменных и функций. Конечно, JavaScript не был бы сам собой, если бы не было особых случаев, в которых всё немного сложнее.
Разрешение имён
Именованные функциональные выражения
Вы можете давать имена функциям, определённым с помощью функциональных выражений, используя синтаксис определения функций. Это не приводит к объявлению функции, а следовательно, имя функции ни добавляется в область видимости, ни поднимается вместе с телом функции в начало области видимости. Вот несколько строк, чтобы проиллюстрировать, что я имею в виду:
Как писать код, обладая такими знаниями
Итак, теперь вы понимаете область видимости и «поднятие» переменных и объявлений функций. Что это означает применительно к написанию кода на JavaScript? Самое главное — всегда объявлять ваши переменные, используя var. Я настаиваю на том, чтобы у вас был ровно один var на область видимости и чтобы он располагался в её начале. Если вы заставите себя так делать, у вас никогда не будет проблем, связанных с «поднятием». Тем не менее, это может привести к тому, что сложно следить за переменными, которые объявлены в текущей области видимости. Я рекомендую использовать JSLint с включённой опцией onevar, чтобы вынудить вас так делать. Если вы будете так всё делать, ваш код будет выглядеть примерно так:
Что говорит стандарт
Если инструкция переменной встречается внутри ОбъявленияФункции, переменные объявляются внутри локальной области видимости для данной функции согласно описанию в разделе 10.1.3. В противном случае они объявляются в глобальной области видимости (т.е. создаются как поля глобального объекта согласно описанию в разделе 10.1.3) с использованием атрибутов свойств < DontDelete >. Переменные создаются, когда происходит вход в область выполнения. Блок не определяет новой области выполнения. Только Программа и ОбъявлениеФункции создают новую область видимости. Переменные инициализируются при создании значением undefined. Переменной, для которой определён Инициализатор, присваивается значение его ВыраженияПрисваивания в момент выполнения ИнструкцииПеременной, а не в момент создания переменной.
Этапы создания и выполнения контекста
Разберем что в действительности делает JavaScript-движок для создания контекста выполнения. Этот процесс проходит в два этапа:
Создание контекста выполнения и “всплытие”
Именно вследствие выполнения этапа создания следующий код будет работать:
Результат выполнения кода:
Это может показаться неожиданным поведением программы, так как вызов функции showText() и обращение к переменной value происходит до их объявления в коде. Но в JavaScript это работает потому, что на этапе создания контекста, до выполнения функции, JavaScript-движок исследует код и формирует контекст, который содержит в себе указатель на текущее Лексическое окружение, связанное с выполняемым кодом.
В предыдущей части говорилось, что лексическое окружение содержит в себе Запись Окружения, но то, что она из себя представляет, мы рассматривали на этапе уже выполненного кода. Например:
Хотя в действительности, Запись Окружения меняется в течение выполнения кода. Она создаётся и начинает формироваться на этапе создания контекста. В неё помещаются все объявленные в коде переменные и функции, тем самым для них резервируется место в памяти еще до выполнения первой строчки кода.
Вот как выглядит запись окружения на этапе создания контекста выполнения
Здесь важно заметить, что на этапе создания контекста, переменные и функциональные выражения, такие как:
Это происходит потому, что сам код еще не выполняется, и никаких операций присваиваний еще не произошло. JavaScript-движок лишь просканировал код и сформировал запись окружения из имен объявленных переменных и функций. Исключением являются обычные объявления функций, такие как:
Они целиком, вместе с телом функции и встроенными свойствами, помещаются в запись окружения
Именно из-за формирования записи окружения и выделения памяти под переменные до выполнения кода к ним можно обращаться до их объявления в программе. Такое поведения называется “всплытие” или hoisting. К сожалению, в некоторых ресурсах всплытие описывают так, что объявление переменной или функции физически поднимается в начало вашего кода, хотя в действительности это не так. На самом же деле, объявления переменных остаются в коде на том же месте, где вы их объявили, только память под них выделяется с самого начала, еще до выполнения кода.
Такая ошибка ReferenceError из-за попытки получить или установить значение let или const переменной до её объявления называется ошибкой “Временной мертвой зоны” (Temporal Dead Zone (TDZ) error).
В некоторых источниках можно встретить утверждение, что переменные let и const вообще не всплывают, на самом деле это не так. Они также попадают в запись окружения, как и var переменные при создании контекста.
Только к ним нельзя получить доступ для чтения или записи до тех пор, пока не будет выполнена строка с объявлением этой переменной на этапе выполнения контекста. Механизм, обеспечивающий такое отличие доступа к переменным let и const от var детально будет рассмотрен в следующий части, а сейчас приведем примеры работы временной мертвой зоны.
Еще одним интересным моментом является поведение оператора typeof при временной мертвой зоне. Оператор typeof возвращает тип данных переменной и часто используется для проверки существования глобальных переменных.
Вообще, как можно заметить из предыдущих примеров этой части курса, чтобы код было легче читать и поддерживать, а также для сокращения возможных ошибок, не стоит полагаться на всплытие и лучше сначала объявлять переменные, а потом их использовать. Так код станет яснее, и вести разработку и поддерживать код будет намного проще.
Выполнение кода. Однопоточность и синхронное выполнение
Рассмотрим пример выполнения кода:
Результат выполнения кода:
При разработке на JavaScript необходимо понимать, что весь код выполняется синхронно в одном потоке выполнения. Это значит, что инструкции программы выполняется поочередно друг за другом, выполнение следующей инструкции начинается только после выполнения текущей, тем самым в определенный момент времени может выполняться только одна инструкция/команда. При такой синхронной модели невозможно приостанавливать выполнение задачи, чтобы в промежутке выполнить другую.
Схематично это можно изобразить так:
Если рассматривать среду браузера, то в нём может много потоков выполнения, отвечающих за рабочие процессы браузера, но с точки зрения выполнения в нём программы JavaScript, то она обрабатывается и выполняется в браузере в одном потоке. Поэтому если в коде JavaScript есть определенная операция, выполнение которой займет много времени, то она будет блокировать выполнение всех дальнейших инструкций, ожидающих её окончания.
Например, вызовем 50 раз (очень неэффективную) функцию, которая ищет простые числа среди довольно больших чисел.
Или, например, метод alert полностью блокирует выполнение последующих операций
Что такое Hoisting в JavaScript
И как пользоваться этим “поднятием”
Возможно, вы уже знаете, что переменные могут “подниматься”. “Hoisting” переводится с английского как “поднятие” и означает понятие, которое было придумано для того, чтобы можно было говорить о замыканиях в JavaScript без указания области видимости переменных.
Перед тем как начать, следует ознакомиться с терминами из статьи, такими как лексическое окружение, обработчики синтаксиса и контексты выполнения.
Теперь рассмотрим то, что, скорее всего, вообще не будет работать в других языках программирования. Вернемся к коду: передвинем вызов функции b() и вывод значения переменной а вверх, в начало кода.
В большинстве языков программирования такая запись выдаст ошибку, поскольку обычно они выполняют код строка за строкой. Так как функция b() еще не была объявлена перед вызовом, мы пока не можем ее использовать. По крайней мере такого поведения следует ожидать. Однако в JavaScript дела обстоят немного иначе.
Консоль выдает ошибку a is not defined (переменная а не определена).
Теперь помещаем переменную внутрь JS-файла.
Такой феномен называется “поднятием” (hoisting).
Описания в интернете могут дать неверное представление об этом процессе. Как правило, в них говорится о том, что переменные и функции в JavaScript поднимаются в самый верх программы из-за движка JS, будто их на самом деле туда переместили, а поэтому они могут работать в любом месте.
Происходит так, будто мы объявили переменную, а значение будет присвоено ей позднее. Но это не то, что было написано. Дело в том, что весь код преобразуется движком JavaScript.
Вот код, который мы писали в начале.
Чтобы разобраться во внутренних процессах JavaScript, нужно копнуть немного глубже в контекст выполнения программы. Дело в том, что он запускается в два этапа. Это и есть причина, по которой переменные и функции JavaScript в некотором роде доступны, даже если были объявлены в коде позже.
Следует помнить, что this создается внутри контекста выполнения программы. Затем создаётся внешнее окружение.
В фазе создания парсер проходит через весь код и начинает настраивать написанное для преобразования. Он распознает места, где мы создали переменные или функции, а затем выделяет пространство в памяти для всех этих данных. И именно этот процесс называют поднятием.
Но JavaScript не перемещает код вверх. На самом деле его движок выделяет место в памяти для всех переменных, лежащих в коде, еще до начала построчного выполнения программы.
Когда код начинает запускаться строка за строкой, он уже имеет доступ ко всем элементам. Однако в случае переменных все немного сложнее. Функции, будучи обработанными парсером, целиком помещаются в память. Вторая фаза (фаза выполнения, когда код выполняется построчно) — это как раз тот момент, когда настраиваются все присваивания, а переменная а получает какое-либо значение.
Все эти процессы происходят, потому что где-то в лексическом окружении языка происходит нечто, представленное ниже.
Это значит, что опираться на поднятие переменных и функций — не лучшая идея, оно может доставить кучу проблем.
Вот как делать не нужно.
Вместо этого лучше сделать так.
Теперь мы уже понимаем, что значит поднятие. Мы и вправду можем вызвать функцию несмотря на то, что она объявлена позже. Это связано с тем, что написанный код не выполняется напрямую. Движок JS обрабатывает его и лишь затем принимает решения. Это немного странно, но так он работает.
Сравнение var, let и const при поднятии
Как вы думаете, каким будет результат вывода программы?
Эта ошибка всплывает из-за Временной мертвой зоны (Temporal Dead Zone), однако не стоит пугаться этого термина. Он обозначает период между созданием переменной и её инициализацией, когда мы не можем получить к ней доступ.
Значит ли это, что все переменные, объявленные с помощью let и const не “поднимаются” в коде? Нет, они тоже поднимаются.