Javascript - быстрорастущий язык программирования, поскольку его можно использовать от браузера к серверу и от мобильных приложений к веб-приложениям. Есть люди, которые любят JS, и есть много разделов, которые полностью ненавидят JS.

Но JS - прекрасный язык ❤

В этом блоге я попытаюсь объяснить, как JS работает за кулисами, и объяснить вам, как область видимости работает в JS. Итак, приступим.

Как JS работает в браузере?

Все браузеры имеют движок (в основном написанный на C и C ++), который преобразует код JS в машинный код и тем самым позволяет браузерам понимать и запускать код. Некоторые популярные браузерные движки - это V8, используемый в Google Chrome, в то время как Safari использует Squirrelfish и тому подобное.

Все в JS происходит внутри контекста выполнения.

Прежде чем идти дальше, давайте поймем, что JS - это синхронный однопоточный язык. Однопоточный означает, что JS может выполнять только одну команду за раз. Синхронный однопоточный означает, что JS может выполнять только одну команду за раз и в определенном порядке, то есть он может перейти к следующей строке только после того, как текущая строка завершит выполнение.

Теперь давайте разберемся, что происходит внутри контекста выполнения.

Чтобы объяснить, что происходит в контексте выполнения, давайте посмотрим на код JS и посмотрим, как он работает.

Когда скрипт загружается в браузере, создается глобальный контекст выполнения. Контекст выполнения выполняет код в 2 этапа, а именно: Компонент памяти и Компонент кода.

На первом этапе внутри компонента памяти всем переменным и функциям будет выделена память как пары ключ-значение. Помните, JS - это синхронный однопоточный язык. Таким образом, он будет запускаться по одной строке за раз. Таким образом, JS будет просматривать весь фрагмент кода построчно и выделять память для переменных и функций.

Строка 1: var n = 8;
n будет присвоено значение undefined , а не 8 . Глобальный контекст выполнения будет выглядеть так -

Вторая строка: функция square () {….}.
Память будет выделена для всего блока кода, и, поскольку это функция, Вся функция будет сохранена. Теперь GEC будет выглядеть так -

Строка 3, строка 4 будет выделять память при вызове функции, поскольку они находятся внутри функции.

Строка 6: var square8 = square (n);
Поведение аналогично строке 1.

Строка 7: var sqaure10 = square (10);
Поведение аналогично строке 1.

В конце содержимого выполнения это будет выглядеть примерно так.

Этап 2. Выполнение кода.

После выделения памяти начинается этап 2 - выполнение кода.
При выполнении кода JS снова просматривает программу сверху вниз и строка за строкой (помните, что синхронный однопоточный ).

Строка 1: var n = 8;
Фактически она помещает 8 внутри «n» вместо undefined. Таким образом, до фазы памяти значение n не определено , а теперь 8 выделяется для «n».

Строка 2: квадрат функции
Поскольку это функция, выделять нечего, поэтому выполняется переход к строке 6.

Строка 6: var square8 = square (n);
Здесь вызывается функция square, и при вызове функции создается новая фаза выполнения внутри компонент кода. Новый созданный контекст выполнения, как и предыдущий, состоит из двух фаз.

Итак, теперь, в фазе памяти, он выделяет память для переменных внутри функции, начиная с параметра. Итак, теперь параметру «num» вместе с переменной «ans» будет выделена память и будет храниться значение undefined.

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

Функция вызывается в строке 6 с аргументом ‘n’, поэтому n, которому присвоено 8, будет помещено в число. Итак, контекст выполнения теперь будет выглядеть так -

Итак, теперь код перейдет к следующей строке, которая является строкой №. 3, где var ans = num * num. Поскольку num = 8, ans будет сохранен как 64. Контекст выполнения будет выглядеть так -

Итак, теперь элемент управления перейдет к строке 4, где он увидит специальное ключевое слово return ans. Таким образом, всякий раз, когда функция видит ключевое слово return, она знает, что это конец, и должна вернуть ans (, который равен 64), в контекст выполнения, в котором была вызвана функция ( строка 6). Таким образом, он найдет значение ans из компонента памяти (локальная память), равное 64, и назначит 64 для square8 вместо undefined. Теперь контекст выполнения будет выглядеть так -

Последнее, что происходит после того, как функция вернулась, - это удаление контекста выполнения вызова функции. Итак, теперь это будет выглядеть так -

Строка 7: var square10 = square (10);
То же поведение, что и в строке 6.

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

Что такое стек вызовов?

Стек вызовов - это стек структуры данных, в котором хранится информация о том, какая строка кода выполняется в данный момент и какая другая функция вызывается изнутри функции.

Как работает стек вызовов?

Когда скрипт загружается, глобальный контекст выполнения помещается внутрь стека вызовов, а при вызове функции создается новый контекст выполнения, который помещается поверх глобального контекста выполнения (в нашей программе строка 6 вызывает функция и square8 помещается поверх глобального контекста выполнения).

По достижении оператора return вызов функции square8 завершается, а затем стек вызовов выталкивает функцию sqaure8, и управление возвращается в глобальный контекст выполнения и продолжает работу с того места, где он оставил т. Е. в строке нет. 7. Эта строка создает новый контекст выполнения при вызове функции (square10) и при достижении оператора return, даже он выскакивает, а затем снова управление возвращается в глобальный контекст выполнения. Теперь больше нет кода для запуска, поэтому глобальный контекст выполнения также удаляется.

Таким образом, стек вызовов предназначен только для управления этими контекстами выполнения. Он поддерживает порядок, в котором происходит выполнение этих контекстов выполнения.

Теперь давайте посмотрим, что такое Scope и как он работает?

В Javascript есть функция под названием Scope. Область действия - это контекст, в котором возможен доступ к переменной / функции. Он определяет видимость переменных в коде. Какая часть программы может иметь доступ к определенной переменной, а какая нет. Это помогает поддерживать безопасность кода. Таким образом мы можем избежать непреднамеренного изменения кода из других частей.

Например, вы управляете магазином футболок в торговом центре. Вы нанимаете продавца для своего магазина. Теперь этот человек должен иметь доступ только к той роли, которая ему ограничена, например, посещение потенциального покупателя, выставление счетов, и он не должен иметь доступа к учетным записям или манипулировать системой видеонаблюдения и т. Д. Но вы, как владелец магазина, можете иметь доступ ко всему. Так же, как и в кодировании, мы можем назначить определенные переменные, к которым можно получить доступ глобально, и определенные переменные, доступ к которым ограничен.

В Javascript существует два типа области действия:
Глобальная область действия
Локальная область действия

Глобальная область действия
Переменная, которая, если объявлена ​​вне функции или фигурных скобок, считается переменной, определенной в глобальной области действия. рассмотрите следующий код:

Теперь к этой переменной ‘a’ можно получить доступ в любом месте кода, даже в функциях.

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

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

Здесь мы получаем ошибку о том, что n уже определено, что не допускает конфликтов имен.

Здесь нет уважения к конфликтам имен, и код работает так, как будто нет ошибки, когда мы объявляем переменные с помощью var.

Поэтому рекомендуется объявлять переменные с помощью let & const и делать это в локальной области видимости, а не в глобальной.

Теперь рассмотрим следующий код:

В приведенном выше коде, когда мы вызываем функцию a (), мы получаем 10 в консоли.

Теперь посмотрим на это:

Функция c (), которая находится внутри функции a (), также имеет доступ к глобальной переменной. В этом примере в консоли также будет напечатано 10.

Теперь посмотрим на этот код:

В этом примере в консоль также будет помещено 10. функция c имеет доступ к переменной b, которая объявлена ​​внутри a ().

Но доступ к локальной переменной из глобальной области невозможен. Проверьте код ниже

Будет выдана ошибка, что b не определено.

Разве не было бы здорово узнать, как работает код? Теперь посмотрим, как работает прицел.

Но прежде чем идти дальше, обратите внимание, что локальные области бывают двух типов, а именно: область действия и область действия блока.

Объем функции. Все переменные, объявленные внутри функции, доступны только внутри функции. Мы не можем получить доступ к переменной вне функции. Посмотрите пример 4 выше.

Область действия блока: когда мы объявляем переменную с помощью const & let внутри фигурной скобки, мы можем получить доступ к переменной только внутри этой фигурной скобки. Проверьте приведенный ниже пример

Обратите внимание на поведение в приведенном выше примере, как можно получить доступ к var даже за пределами блока фигурных скобок, тогда как переменные, объявленные с помощью «let» и «const», недоступны, и это вызывает ошибку.

А теперь давайте посмотрим, как прицел работает за кулисами?

Рассмотрим следующий код:

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

Лексическое окружение - это локальная память вместе с лексическим окружением ее родителя.

Стек вызовов будет выглядеть так при запуске первой строки

После первой строки никакие дальнейшие переменные в глобальной области не присваиваются. Итак, следующая функция вызывается в строке 9.

Когда выполняется строка 9, создается новый контекст выполнения для функции a () с компонентом памяти и компонентом кода поверх GEC. Теперь стек вызовов будет выглядеть так:

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

Переменная b сначала будет определена как неопределенная в компоненте памяти, а затем, когда начнется фаза компонента кода, она будет определена с присвоенными им значениями, например, b = 10. Весь экземпляр функции c () будет сохранен внутри компонента памяти. .

Строка 3: это вызов функции. Итак, снова новый контекст выполнения будет помещен поверх стека вызовов. Теперь стек вызовов будет выглядеть так

Вот так код выполняется внутри GEC, и новый контекст выполнения создается всякий раз, когда выполняется функция. Теперь давайте посмотрим, что такое лексическая среда.

Лексическая среда - это локальная память вместе с лексической областью ее родителя. Слово лексическое означает иерархию или последовательность. В приведенном выше примере функция c () лексически находится внутри функции a (). Таким образом, это означает, что функция c () может обращаться к переменным, которые объявлены внутри c () по умолчанию вместе с переменными, которые объявлены внутри функции a ().

Таким образом, всякий раз, когда создается контекст выполнения функции c (), мы также получаем ссылку на лексическое окружение ее родителя. То же самое относится к функции a (), которая получает ссылку на лексическое окружение GEC.

Теперь обновленный стек вызовов выглядит так:

Таким образом, функция c () будет ссылаться на свою собственную память вместе со ссылкой на лексическое окружение своего родителя (функция a ()) и GEC.

Функция a () будет иметь доступ к своей собственной памяти вместе со ссылкой на лексическое окружение своего родителя, являющегося GEC.

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

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

Как используется лексическая среда?

рассмотрим следующий код:

Стек вызовов будет таким же, как указано выше. Теперь, когда движок JS встречает строку 5, он пытается получить доступ к переменной b в локальной памяти функции c (). Его там не найдешь.

Поскольку функция c () находится внутри лексического окружения своей родительской функции a (), она увидит, что у нее есть ссылка на ту же самую, и теперь получит доступ к var b = 10 и напечатает 10 в консоль.

Теперь, если предположим, что у нас нет var b = 10; внутри функции a (), он перейдет к родительскому элементу функции a (), являющемуся GEC. Там он будет искать переменную b, и если она есть, то будет напечатано значение, присвоенное b.

В приведенном выше примере этого нет и в GEC. Таким образом, он перейдет к тому, что родительский элемент GEC будет нулевым. Поэтому, когда вы дойдете до этого места, он остановится, а print b не определен.

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

Цепочка областей видимости - это не что иное, как вся лексическая среда вместе со ссылками на родительские ссылки.

Подведение итогов

Когда скрипт загружается в браузере, создается глобальный контекст выполнения (GEC). Этот контекст выполнения состоит из двух компонентов: компонент памяти и компонент кода.

На первом этапе механизм JS просматривает всю программу и выделяет память всем переменным и функциям.

На второй фазе, фазе выполнения кода, механизм JS снова выполняет всю программу, строка за строкой (Однопоточная синхронная), и выполняет код. новый контекст выполнения создается каждый раз при вызове функции.

Все это делается внутри стека вызовов. Как только код будет полностью выполнен, стек вызовов выталкивает GEC и становится пустым.

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

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

Надеюсь, вам понравилось это читать, и вы узнали что-то новое. Если у вас есть вопросы, не стесняйтесь спросить. Я отвечу, как только смогу.

Вы можете узнать больше о том, как работает Javascript здесь. Все спасибо Акшаю Шайни за создание таких великолепных видео, а также спасибо Танай Пратап, который подталкивает множество людей к ведению блогов.