Область видимости в 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. Переменной, для которой определён Инициализатор, присваивается значение его ВыраженияПрисваивания в момент выполнения ИнструкцииПеременной, а не в момент создания переменной.
Что такое 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 не “поднимаются” в коде? Нет, они тоже поднимаются.
Разбираемся с “поднятием” (hoisting) в JavaScript
Feb 23, 2018 · 9 min read
👉 Мой Твиттер — там много из мира фронтенда, да и вообще поговорим🖖. Подписывайтесь, будет интересно: ) ✈️
В этом руководстве вы изучите то, как срабатывает всеми извезтный механизм “ поднятия” в JavaScript. Ну или в оригинальном названии hoisting. Однако, перед тем как углубиться в детали, давайте узнаем что это вообще такое и как оно работает на самом деле.
Поднятие или hoisting — это механизм в JavaScript, в котором переменные и объявления функций, передвигаются вверх своей области видимости перед тем, как код будет выполнен.
Как следств и е, это означает то, что совершенно неважно где были объявлены функция или переменные, все они передвигаются вверх своей области видимости, вне зависимости от того локальная она или же глобальная.
Стоит отметить то, что механизм “ поднятия” передвигает только объявления функции или переменной. Назначения переменным остаются на своих местах.
В общем, если вы когда-либо удивлялись, почему вы могли вызывать функции перед тем, как они написаны в коде, то читайте дальше.
Undefined vs ReferenceError
Перед тем, как мы начнем серьёзно углубляться в этот вопрос, давайте проясним несколько вещей.
Вторым заключением будет:
В JavaScript, ReferenceError появляется при попытке доступа к предварительно необъявленной переменной.
Поведение JavaScript при работе с переменными становится довольно утонченным делом из-за «поднятия». Мы увидим это более детально в следующих параграфах.
«Поднятие» переменных
Ниже вы видите цикл работы JavaScript, показывающий последовательность, в которой происходит объявление и инициализация переменных.
Однако, в JavaScript мы можем объявлять и инициализировать наши переменные одновременно, как в этом ну просто самом распространенном примере:
Запомните и держите в уме одну важную деталь, JavaScript непреклонно сначала объявляет, а уже затем инициализирует наши переменные.
Как упоминалось ранее, все переменные и объявления функций поднимаются вверх своей области видимости. Мне также стоит добавить, что объявление переменных происходит перед выполнением кода.
Но однако, необъявленные переменные не существуют до тех пор, пока код назначающий их не будет выполнен. Следовательно, указание значения для необъявленной переменной, тут же создаёт её как глобальную переменную, когда назначение будет выполнено. Это говорит о том, что все необъявленные переменные это по факту глобальные переменные.
Чтобы продемонстрировать это поведение, давайте посмотрим на следующий код.
Так как это одна из причуд работы JavaScript с переменными, рекомендуется всегда объявлять их, вне зависимости от их положения в коде, в функции они или в глобальной области видимости.
Это ясно указывает на то, как движок JavaScript должен с ними работать во время выполнения кода.
Глобальные переменные
Почему так произошло?
JavaScript «поднял» объявление переменной. Вот как это выглядит для движка JavaScript:
Переменные в области видимости функции
Вот как движок видит код выше:
Чтобы избегать таких ловушек, нам всегда нужно убеждаться в том, что мы объявляем и инициализируем переменные перед их использованием.
Strict Mode или «Строгий режим»
Устраняет некоторые скрытые ошибки в JavaScript, изменяя их на явную выдачу ошибок, которые будут в последствии выданы движком.
Устраняет ошибки, которые затрудняют движкам JavaScript выполнять оптимизацию.
Запрещает некоторый синтаксис, который с большой долей вероятности будет уже идти из коробки в будущих версиях JavaScript.
Мы включаем « строгий режим», заранее указывая в нашем файле или функции следующее:
Тем не менее, use-strict ведет себя по разному в разных браузерах, так что будет вполне благоразумно провести тестирование функционала перед использованием в работе.
Тут нам представляет интерес то, как этот стандарт влияет на объявление и инициализацию JavaScript переменных.
Перед тем как идти дальше, стоит отметить то, что переменные объявленные через let заключены в область видимости блока, а не функции. Это очень важно, но это не должно нас сейчас волновать.
Вкратце, это просто говорит о том, что область видимости переменной привязана к блоку, в котором она объявлена, а не к функции в которой она объявлена.
Это еще раз доказывает то, что надо сначала объявлять наши переменные.
Следовательно, чтобы не напороться на предыдущие ошибки, нам нужно сначала объявить переменную, а потом назначить ей значение и только потом уже её использовать.
const была представлена в es6 для того, чтобы можно было сделать неизменные переменные. Да, именно это, переменные значение которых не может быть изменено или переназначено.
Давайте посмотрим, что происходит если мы попытаемся переназначить значение, прикрепленное к const переменной.
Как const изменяет объявление переменной? Давайте посмотрим.
Тоже самое происходит при использовании const в функциях.
Наш линтер быстренько информирует нас об этом просчете:
Следовательно, константные переменные должны быть объявлены и инициализированы перед использованием.
Поднятие функций
JavaScript функции могут классифицироваться как объявленные функции, так и как функциональные выражения. Далее мы узнаем как «поднятие» влияет на оба типа.
Такие функции полностью поднимаются вверх кода. Теперь понятно почему JavaScript позволяет нам вызывать функции прежде, чем мы их объявим по упоминанию в коде.
Функциональные выражения, однако, не поднимаются.
Как мы можем видеть выше, объявление переменной var expression поднимается, но его назначение как функции — нет. Следовательно, движок выдаст TypeError как увидит expression в виде переменой, а не функции.
Порядок по приоритетам
Очень важно помнить несколько вещей, объявляя JavaScript функции и переменные.
Назначение переменных имеет приоритет перед объявлением функции.
Объявление функции имеет приоритет перед объявлением переменной.
Объявления функций «поднимаются» над объявлением переменных, но не над их назначениями.
Давайте посмотрим как это работает.
Назначение переменной над объявлением функции.
Объявление функции над объявлением переменной.
Даже если мы изменим позиции объявлений, JavaScript движок все равно бы взял double функцию.
«Поднятие» классов
Классы в JavaScript могут также классифицироваться как объявления и выражения.
Так же как и свои коллеги функции, JavaScript классы при объявлении « поднимаются». Тем не менее, они остаются неинициализированными до определения. Это говорит о том, что вам надо объявить класс перед тем, как его использовать.
Если мы взглянем на линтер, то там мы увидим полезный совет.
Поэтому, чтобы получить доступ к классу, вам нужно сначала его объявить.
Так же как и свои коллеги по функциям, выражения классов не « поднимаются».
Вот пример анонимного варианта выражения класса.
А вот пример с названным выражением класса.
А вот правильный порядок:
Предостережение
Заключение
Давайте подведем итоги того, что мы изучили:
1. Нам нужно взять в привычку, объявлять и инициализировать JavaScript переменные перед использованием.
2. Использование strict mode в JavaScript es5 может помочь выявить необъявленные переменные.
Всплытие переменных и функций
:pencil: Hoisting
Поднятие или hoisting — это механизм в JavaScript, в котором переменные и объявления функций, передвигаются вверх своей области видимости перед тем, как код будет выполнен.
ℹ️ note
👀 Рассмотрим следующий пример:
Объяснение 🎓
Поднятие функций
Одним из преимуществ JavaScript является помещение объявления функций в память, перед выполнением любого сегмента кода. Объявления функций поднимаются, но они идут на самый верх, поэтому они будут находиться над всеми объявлениями переменных. Это позволяет нам использовать функцию до того, как мы объявим её в своем коде. Например:
Undefined vs ReferenceError
В JavaScript, ReferenceError появляется при попытке доступа к предварительно необъявленной переменной.
❗️ Важно
JavaScript непреклонно сначала объявляет, а уже затем инициализирует наши переменные.
Поднятие const, let и var
Ключевое слово var
Код выше, из-за поднятия эквивалентен приведенному коду ниже.
Ключевые слова const / let
const и let имеют область видимости блока.
❗️ Важно
выбрасывается при попытке доступа к ним. Они будут инициализированы только после того, как операторы let / const будут определены. Всё что до, называется временной мертвой зоной.
:pencil: Временная мертвая зона
Это не синтаксическое местоположение, а время между созданием переменной (области) и инициализацией. Ссылка на переменную в коде над объявлением не является ошибкой, если этот код не выполняется (например, тело функции или просто мертвый код), но ошибка будет выдана, если мы запросим доступ к переменной до её инициализации.
Экземпляры var и let могут быть инициализированы без значения, в то время как const выдаст ошибку ReferenceError
, и если попытаться объявить её без одновременного присвоения ей значения.
Так что const myName = ‘Alex’ будет работать, но const myName; myName = ‘Alex’; не будет.
Если попробовать повторить тоже самое с let
🔥 Важно
let не создает свойств в глобальном объекте.
Обратите внимание, что отсутствие явных фигурных скобок не меняет принцип поведения переменных, объявленных с помощью директивы ` let
Самостоятельная работа 👩💻👨💻
Задание :one:
Поверните данную строку в правильном направлении, периодически удаляя одну букву с конца строки и прикрепляя ее к передней части.
подсказка: используйте substring()
Задание :two:
Напишите функцию, чтобы найти наиболее частый элемент в заданном массиве.
Задание :three:
Напишите функцию для поиска и удаления повторяющихся значений в массиве. Функция должна возвращать массив повторяющихся значений.
Поднятие в JS (Hoisting в Javascript + 3 примера)
В этой статье мы с вами разберемся как что такое, и как работает поднятие (Hoisting) в Javascript. Эта одна из базовых тем в ванильном JS и вы однозначно наткнетесь на нее в одном из интервью при устройстве на работу.
Поднятие в JS (пример с функцией)
Для нашего примера создадим функцию letsGo и попробуем ее вызвать до ее создания.
Наша функция запускается, и мы получаем строку «Go!!» в нашей консоли. Это происходит, так как срабатывает мехнаизм «поднятие» в Javascript.
То есть, «под капотом» компилятор JS «поднимает» все строчки, где объявляются функции на самый верх.
Ваглядит это так:
Теперь, давайте немного расширим наш пример и создадим еще одну функцию add.
Мы видим, что наша вторая функция add также срабатывает и получаем результат сложения в консоли. То есть, опять, сработал механизм «поднятия» в JS, который поднял весь код, где объявляются функции на самый верх.
Применительно к функциям, «поднятие» работает только с объявлением функций!
Поднятие в JS не работает при использовании функциональных выражений, стрелочных функций и любых других способов создания функций.
То есть, если мы попробуем использовать функциональное выражение и запустить функцию до ее создания, то получим ошибку:
Если решим написать то же самое, используя стрелочную функцию, тоже получим ошибку («поднятие» не работает):
Поднятие в Javascript (пример с переменной var)
Давайте, ради интереса, также выведем в лог произвольную переменную, которой у нас вообще не существует:
То есть, когда мы выводим в лог значение переменной years, до ее создания, происходит следующее:
Обработчики Событий в JS (как работает addEventListener)
Замыкания в Javascript (что такое замыкание в JS + 3 примера)

















