Как посмотреть стек вызовов в браузере

Обновлено: 07.07.2024

Если вы — JavaScript-разработчик или хотите им стать, это значит, что вам нужно разбираться во внутренних механизмах выполнения JS-кода. В частности, понимание того, что такое контекст выполнения и стек вызовов, совершенно необходимо для освоения других концепций JavaScript, таких, как поднятие переменных, области видимости, замыкания. Материал посвящён контексту выполнения и стеку вызовов в JavaScript.

Контекст выполнения (execution context) — это, если говорить упрощённо, концепция, описывающая окружение, в котором производится выполнение кода на JavaScript. Код всегда выполняется внутри некоего контекста.

В JavaScript существует три типа контекстов выполнения:

  • Глобальный контекст выполнения. Это базовый, используемый по умолчанию контекст выполнения. Если некий код находится не внутри какой-нибудь функции, значит этот код принадлежит глобальному контексту. Глобальный контекст характеризуется наличием глобального объекта, которым, в случае с браузером, является объект window , и тем, что ключевое слово this указывает на этот глобальный объект. В программе может быть лишь один глобальный контекст.
  • Контекст выполнения функции. Каждый раз, когда вызывается функция, для неё создаётся новый контекст. Каждая функция имеет собственный контекст выполнения. В программе может одновременно присутствовать множество контекстов выполнения функций. При создании нового контекста выполнения функции он проходит через определённую последовательность шагов, о которой мы поговорим ниже.
  • Контекст выполнения функции eval . Код, выполняемый внутри функции eval , также имеет собственный контекст выполнения. Однако функцией eval пользуются очень редко, поэтому здесь мы об этом контексте выполнения говорить не будем.

Стек выполнения (execution stack), который ещё называют стеком вызовов (call stack), это LIFO-стек, который используется для хранения контекстов выполнения, создаваемых в ходе работы кода.

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

Движок выполняет функцию, контекст выполнения которой находится в верхней части стека. Когда работа функции завершается, её контекст извлекается из стека и управление передаётся тому контексту, который находится в предыдущем элементе стека.

Изучим эту идею с помощью следующего примера:


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


Когда вышеприведённый код загружается в браузер, JavaScript-движок создаёт глобальный контекст выполнения и помещает его в текущий стек вызовов. При выполнении вызова функции first() движок создаёт для этой функции новый контекст и помещает его в верхнюю часть стека.

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

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

До сих пор мы говорили о том, как JS-движок управляет контекстами выполнения. Теперь поговорим о том, как контексты выполнения создаются, и о том, что с ними происходит после создания. В частности, речь идёт о стадии создания контекста выполнения и о стадии выполнения кода.

Стадия создания контекста выполнения

Перед выполнением JavaScript-кода создаётся контекст выполнения. В процессе его создания выполняются три действия:

  1. Определяется значение this и осуществляется привязка this (this binding).
  2. Создаётся компонент LexicalEnvironment (лексическое окружение).
  3. Создаётся компонент VariableEnvironment (окружение переменных).

Концептуально контекст выполнения можно представить так:


Привязка this

В глобальном контексте выполнения this содержит ссылку на глобальный объект (как уже было сказано, в браузере это объект window ).

В контексте выполнения функции значение this зависит от того, как именно была вызвана функция. Если она вызвана в виде метода объекта, тогда значение this привязано к этому объекту. В других случаях this привязывается к глобальному объекту или устанавливается в undefined (в строгом режиме). Рассмотрим пример:


Лексическое окружение

В соответствии со спецификацией ES6, лексическое окружение (Lexical Environment) — это термин, который используется для определения связи между идентификаторами и отдельными переменными и функциями на основе структуры лексической вложенности ECMAScript-кода. Лексическое окружение состоит из записи окружения (Environment Record) и ссылки на внешнее лексическое окружение, которая может принимать значение null .

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

В лексическом окружении имеется два компонента:

  1. Запись окружения. Это место, где хранятся объявления переменных и функций.
  2. Ссылка на внешнее окружение. Наличие такой ссылки говорит о том, что у лексического окружения есть доступ к родительскому лексическому окружению (области видимости).

Существует два типа лексических окружений:

  1. Глобальное окружение (или глобальный контекст выполнения) — это лексическое окружение, у которого нет внешнего окружения. Ссылка глобального окружения на внешнее окружение представлена значением null . В глобальном окружении (в записи окружения) доступны встроенные сущности языка (такие, как Object , Array , и так далее), которые связаны с глобальным объектом, там же находятся и глобальные переменные, определённые пользователем. Значение this в этом окружении указывает на глобальный объект.
  2. Окружение функции, в котором, в записи окружения, хранятся переменные, объявленные пользователем. Ссылка на внешнее окружение может указывать как на глобальный объект, так и на внешнюю по отношении к рассматриваемой функции функцию.

Существует два типа записей окружения:

  1. Декларативная запись окружения, которая хранит переменные, функции и параметры.
  2. Объектная запись окружения, которая используется для хранения сведений о переменных и функциях в глобальном контексте.

В результате, в глобальном окружении запись окружения представлена объектной записью окружения, а в окружении функции — декларативной записью окружения.

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

Лексическое окружение можно представить в виде следующего псевдокода:


Окружение переменных

Окружение переменных (Variable Environment) — это тоже лексическое окружение, запись окружения которого хранит привязки, созданные посредством команд объявления переменных ( VariableStatement ) в текущем контексте выполнения.

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

В ES6 существует одно различие между компонентами LexicalEnvironment и VariableEnvironment . Оно заключается в том, что первое используется для хранения объявлений функций и переменных, объявленных с помощью ключевых слов let и const , а второе — только для хранения привязок переменных, объявленных с использованием ключевого слова var .

Рассмотрим примеры, иллюстрирующие то, что мы только что обсудили:


Схематичное представление контекста выполнения для этого кода будет выглядеть так:


Как вы, вероятно, заметили, переменные и константы, объявленные с помощью ключевых слов let и const , не имеют связанных с ними значений, а переменным, объявленным с помощью ключевого слова var , назначено значение undefined .

Это так из-за того, что во время создания контекста в коде осуществляется поиск объявлений переменных и функций, при этом объявления функций целиком хранятся в окружении. Значения переменных, при использовании var , устанавливаются в undefined , а при использовании let или const остаются неинициализированными.

Именно поэтому можно получить доступ к переменным, объявленным с помощью var , до их объявления (хотя они и будут иметь значение undefined ), но, при попытке доступа к переменным или константам, объявленным с помощью let и const , выполняемой до их объявления, возникает ошибка.

То что мы только что описали, называется «поднятием переменных» (Hoisting). Объявления переменных «поднимаются» в верхнюю часть их лексической области видимости до выполнения операций присвоения им каких-либо значений.

Стадия выполнения кода

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

Обратите внимание на то, что если в процессе выполнения кода JS-движок не сможет найти в месте объявления значение переменной, объявленной с помощью ключевого слова let , он присвоит этой переменной значение undefined .

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

Если вы — JavaScript-разработчик или хотите им стать, это значит, что вам нужно разбираться во внутренних механизмах выполнения JS-кода. В частности, понимание того, что такое контекст выполнения и стек вызовов, совершенно необходимо для освоения других концепций JavaScript, таких, как поднятие переменных, области видимости, замыкания. Материал, перевод которого мы сегодня публикуем, посвящён контексту выполнения и стеку вызовов в JavaScript.




Контекст выполнения

Контекст выполнения (execution context) — это, если говорить упрощённо, концепция, описывающая окружение, в котором производится выполнение кода на JavaScript. Код всегда выполняется внутри некоего контекста.

▍Типы контекстов выполнения

В JavaScript существует три типа контекстов выполнения:

  • Глобальный контекст выполнения. Это базовый, используемый по умолчанию контекст выполнения. Если некий код находится не внутри какой-нибудь функции, значит этот код принадлежит глобальному контексту. Глобальный контекст характеризуется наличием глобального объекта, которым, в случае с браузером, является объект window , и тем, что ключевое слово this указывает на этот глобальный объект. В программе может быть лишь один глобальный контекст.
  • Контекст выполнения функции. Каждый раз, когда вызывается функция, для неё создаётся новый контекст. Каждая функция имеет собственный контекст выполнения. В программе может одновременно присутствовать множество контекстов выполнения функций. При создании нового контекста выполнения функции он проходит через определённую последовательность шагов, о которой мы поговорим ниже.
  • Контекст выполнения функции eval . Код, выполняемый внутри функции eval , также имеет собственный контекст выполнения. Однако функцией eval пользуются очень редко, поэтому здесь мы об этом контексте выполнения говорить не будем.

Стек выполнения

Стек выполнения (execution stack), который ещё называют стеком вызовов (call stack), это LIFO-стек, который используется для хранения контекстов выполнения, создаваемых в ходе работы кода.

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

Движок выполняет функцию, контекст выполнения которой находится в верхней части стека. Когда работа функции завершается, её контекст извлекается из стека и управление передаётся тому контексту, который находится в предыдущем элементе стека.

Изучим эту идею с помощью следующего примера:


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


Состояние стека вызовов

Когда вышеприведённый код загружается в браузер, JavaScript-движок создаёт глобальный контекст выполнения и помещает его в текущий стек вызовов. При выполнении вызова функции first() движок создаёт для этой функции новый контекст и помещает его в верхнюю часть стека.

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

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

О создании контекстов и о выполнении кода

До сих пор мы говорили о том, как JS-движок управляет контекстами выполнения. Теперь поговорим о том, как контексты выполнения создаются, и о том, что с ними происходит после создания. В частности, речь идёт о стадии создания контекста выполнения и о стадии выполнения кода.

▍Стадия создания контекста выполнения

Перед выполнением JavaScript-кода создаётся контекст выполнения. В процессе его создания выполняются три действия:

  1. Определяется значение this и осуществляется привязка this (this binding).
  2. Создаётся компонент LexicalEnvironment (лексическое окружение).
  3. Создаётся компонент VariableEnvironment (окружение переменных).

Привязка this

В глобальном контексте выполнения this содержит ссылку на глобальный объект (как уже было сказано, в браузере это объект window ).

В контексте выполнения функции значение this зависит от того, как именно была вызвана функция. Если она вызвана в виде метода объекта, тогда значение this привязано к этому объекту. В других случаях this привязывается к глобальному объекту или устанавливается в undefined (в строгом режиме). Рассмотрим пример:

Лексическое окружение

В соответствии со спецификацией ES6, лексическое окружение (Lexical Environment) — это термин, который используется для определения связи между идентификаторами и отдельными переменными и функциями на основе структуры лексической вложенности ECMAScript-кода. Лексическое окружение состоит из записи окружения (Environment Record) и ссылки на внешнее лексическое окружение, которая может принимать значение null .

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

В лексическом окружении имеется два компонента:

  1. Запись окружения. Это место, где хранятся объявления переменных и функций.
  2. Ссылка на внешнее окружение. Наличие такой ссылки говорит о том, что у лексического окружения есть доступ к родительскому лексическому окружению (области видимости).
  1. Глобальное окружение (или глобальный контекст выполнения) — это лексическое окружение, у которого нет внешнего окружения. Ссылка глобального окружения на внешнее окружение представлена значением null . В глобальном окружении (в записи окружения) доступны встроенные сущности языка (такие, как Object , Array , и так далее), которые связаны с глобальным объектом, там же находятся и глобальные переменные, определённые пользователем. Значение this в этом окружении указывает на глобальный объект.
  2. Окружение функции, в котором, в записи окружения, хранятся переменные, объявленные пользователем. Ссылка на внешнее окружение может указывать как на глобальный объект, так и на внешнюю по отношении к рассматриваемой функции функцию.
  1. Декларативная запись окружения, которая хранит переменные, функции и параметры.
  2. Объектная запись окружения, которая используется для хранения сведений о переменных и функциях в глобальном контексте.

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

Лексическое окружение можно представить в виде следующего псевдокода:

Окружение переменных

Окружение переменных (Variable Environment) — это тоже лексическое окружение, запись окружения которого хранит привязки, созданные посредством команд объявления переменных ( VariableStatement ) в текущем контексте выполнения.

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

В ES6 существует одно различие между компонентами LexicalEnvironment и VariableEnvironment . Оно заключается в том, что первое используется для хранения объявлений функций и переменных, объявленных с помощью ключевых слов let и const , а второе — только для хранения привязок переменных, объявленных с использованием ключевого слова var .

Рассмотрим примеры, иллюстрирующие то, что мы только что обсудили:


Схематичное представление контекста выполнения для этого кода будет выглядеть так:


Как вы, вероятно, заметили, переменные и константы, объявленные с помощью ключевых слов let и const , не имеют связанных с ними значений, а переменным, объявленным с помощью ключевого слова var , назначено значение undefined .

Это так из-за того, что во время создания контекста в коде осуществляется поиск объявлений переменных и функций, при этом объявления функций целиком хранятся в окружении. Значения переменных, при использовании var , устанавливаются в undefined , а при использовании let или const остаются неинициализированными.

Именно поэтому можно получить доступ к переменным, объявленным с помощью var , до их объявления (хотя они и будут иметь значение undefined ), но, при попытке доступа к переменным или константам, объявленным с помощью let и const , выполняемой до их объявления, возникает ошибка.

Только что мы только что описали, называется «поднятием переменных» (Hoisting). Объявления переменных «поднимаются» в верхнюю часть их лексической области видимости до выполнения операций присвоения им каких-либо значений.

▍Стадия выполнения кода

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

Обратите внимание на то, что если в процессе выполнения кода JS-движок не сможет найти в месте объявления значение переменной, объявленной с помощью ключевого слова let , он присвоит этой переменной значение undefined .

Итоги

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

С помощью окна Стек вызовов можно просматривать вызовы функций и процедур, которые в данный момент находятся в стеке. В окне Стек вызовов показан порядок вызова методов и функций. Стек вызовов хорошо подходит для изучения и анализа потока выполнения приложения.

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

[Frames below may be incorrect and/or missing, no symbols loaded for name.dll]

Окно Стек вызовов аналогично перспективе "Отладка" в некоторых интегрированных средах разработки, например Eclipse.

Отображаемые диалоговые окна и команды меню могут отличаться от описанных здесь в зависимости от текущих параметров или выпуска. Чтобы изменить параметры, выберите в меню Сервис пункт Импорт и экспорт параметров. См. раздел Сброс параметров.

Просмотр стека вызовов в отладчике

Во время отладки в меню Отладка выберите Окна > Стек вызовов.

Окно стека вызовов

Желтая стрелка указывает на кадр стека, в котором находится указатель выполнения. По умолчанию это кадр стека, сведения которого отображаются в окнах: исходного кода, Локальные, Контрольные значения, Видимые и Дизассемблированный код. Чтобы изменить контекст отладчика на другой кадр стека, переключитесь на другой кадр стека.

Отображение непользовательского кода в окне "Стек вызовов"

  • Щелкните правой кнопкой мыши в окне Стек вызовов и выберите в контекстном меню пункт Показать внешний код.

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

Переключение на другой кадр стека (изменение контекста отладчика)

В окне Стек вызовов щелкните правой кнопкой кадр стека, код и данные которого нужно просмотреть.

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

Выберите пункт Перейти к кадру.

Рядом с выбранным кадром стека появится зеленая стрелка с фигурным концом. Указатель выполнения остается в исходном кадре, который по-прежнему отмечен желтой стрелкой. При выборе команд Шаг или Продолжить в меню Отладка выполнение продолжится с исходного, а не с выбранного кадра.

Просмотр исходного кода функции в стеке вызовов

  • В окне Стек вызовов щелкните правой кнопкой мыши функцию, исходный код которой нужно увидеть, и выберите пункт К исходному коду.

Выполнение кода до определенной функции из окна "Стек вызовов"

  • В окне Стек вызовов выберите функцию, щелкните ее правой кнопкой мыши и выберите команду Выполнить до курсора.

Установка точки останова в точке выхода вызова функции

Отображение вызовов в другой поток или из него

  • Щелкните правой кнопкой мыши в окне Стек вызовов и выберите пункт Включить вызовы между потоками.

Визуальная трассировка стека вызовов

В Visual Studio Enterprise (только) можно просматривать карты кода для стека вызовов во время отладки.

В окне Стек вызовов откройте контекстное меню. Выберите Показать стек вызовов на карте кода (CTRL + SHIFT + ` ).

  • В окне Стек вызовов щелкните правой кнопкой мыши функцию, дизассемблированный код которой нужно увидеть, и выберите пункт К дизассемблированному коду.

Включение или отключение отображения дополнительных сведений

  • Щелкните правой кнопкой мыши в окне Стек вызовов и установите или снимите флажок Показать<the information that you want> .

Чтобы загрузить символы

В окне Стек вызовов щелкните правой кнопкой какой-либо кадр стека, для которого не загружены символы. Кадр затеняется.

Укажите на параметр Загрузить символы, а затем выберите Серверы символов (Майкрософт) (если доступно) или перейдите по пути к символам.

Установка пути к символам

В окне Стек вызовов выберите пункт Параметры символов из контекстного меню.

Появится диалоговое окно Параметры, открытое на странице Символы.

Выберите Параметры символов.

В диалоговом окне Параметры щелкните значок "Папка".

В поле Места размещения файлов символов (.pdb) появится курсор.

Введите путь к каталогу с символами на компьютере, на котором производится отладка. При локальной и удаленной отладке это путь на локальном компьютере.

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

Отслеживание переменных

Результат выполнения программы:


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


Обратите внимание, вы можете навести курсор мыши на любую другую переменную (и на любой строке):



Появится специальное окно с текущим значением переменной:


Хорошо, теперь закройте это окно.


Значение переменной х должно поменяться на 2 . Проверьте!

Окно просмотра

В Visual Studio для отображения окна просмотра вам нужно перейти в "Отладка" > "Окна" > "Контрольные значения" > "Контрольные значения 1" :


Вы должны увидеть следующее:

Пока что в этом окне ничего нет, так как вы еще ничего в него не добавили. Есть 2 пути:

Ввести имя переменной, которую нужно отслеживать, в колонку "Имя" в окне просмотра.

Выделить переменную, которую нужно отслеживать > ПКМ > "Добавить контрольное значение" .

Попробуйте добавить переменную х в окно просмотра:


Стек вызовов

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


Укажите точку останова в функции CallC(), а затем запустите отладку. Программа выполнится до точки останова.

Несмотря на то, что вы знаете, что сейчас выполняется CallC(), в программе есть два вызова CallC(): в функции CallB() и в функции CallA(). Какая функция ответственна за вызов CallC() в данный момент? Стек вызовов нам это покажет:




Заключение

Читайте также: