Контекст выполнения и стек вызовов в JavaScript
Если вы — 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-кода создаётся контекст выполнения. В процессе его создания выполняются три действия:
- Определяется значение this и осуществляется привязка this (this binding).
- Создаётся компонент LexicalEnvironment (лексическое окружение).
- Создаётся компонент VariableEnvironment (окружение переменных).
Концептуально контекст выполнения можно представить так:
Привязка this
В глобальном контексте выполнения this содержит ссылку на глобальный объект (как уже было сказано, в браузере это объект window ).
В контексте выполнения функции значение this зависит от того, как именно была вызвана функция. Если она вызвана в виде метода объекта, тогда значение this привязано к этому объекту. В других случаях this привязывается к глобальному объекту или устанавливается в undefined (в строгом режиме). Рассмотрим пример:
Лексическое окружение
В соответствии со спецификацией ES6, лексическое окружение (Lexical Environment) — это термин, который используется для определения связи между идентификаторами и отдельными переменными и функциями на основе структуры лексической вложенности ECMAScript-кода. Лексическое окружение состоит из записи окружения (Environment Record) и ссылки на внешнее лексическое окружение, которая может принимать значение null .
Проще говоря, лексическое окружение — это структура, которая хранит сведения о соответствии идентификаторов и переменных. Под «идентификатором» здесь понимается имя переменной или функции, а под «переменной» — ссылка на конкретный объект (в том числе — на функцию) или примитивное значение.
В лексическом окружении имеется два компонента:
- Запись окружения. Это место, где хранятся объявления переменных и функций.
- Ссылка на внешнее окружение. Наличие такой ссылки говорит о том, что у лексического окружения есть доступ к родительскому лексическому окружению (области видимости).
Существует два типа лексических окружений:
- Глобальное окружение (или глобальный контекст выполнения) — это лексическое окружение, у которого нет внешнего окружения. Ссылка глобального окружения на внешнее окружение представлена значением null . В глобальном окружении (в записи окружения) доступны встроенные сущности языка (такие, как Object , Array , и так далее), которые связаны с глобальным объектом, там же находятся и глобальные переменные, определённые пользователем. Значение this в этом окружении указывает на глобальный объект.
- Окружение функции, в котором, в записи окружения, хранятся переменные, объявленные пользователем. Ссылка на внешнее окружение может указывать как на глобальный объект, так и на внешнюю по отношении к рассматриваемой функции функцию.
Существует два типа записей окружения:
- Декларативная запись окружения, которая хранит переменные, функции и параметры.
- Объектная запись окружения, которая используется для хранения сведений о переменных и функциях в глобальном контексте.
В результате, в глобальном окружении запись окружения представлена объектной записью окружения, а в окружении функции — декларативной записью окружения.
Обратите внимание на то, что в окружении функции декларативная запись окружения, кроме того, содержит объект 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-разработчиком, знать всё это и не обязательно, если у вас имеется некоторое понимание вышеописанных концепций, это поможет вам лучше и глубже разобраться с другими механизмами языка, с такими, как поднятие переменных, области видимости, замыкания.
Context
Context – это объект, который предоставляет доступ к базовым функциям приложения: доступ к ресурсам, к файловой системе, вызов активности и т.д. Activity является подклассом Context, поэтому в коде мы можем использовать её как ИмяАктивности.this (напр. MainActivity.this), или укороченную запись this. Классы Service, Application и др. также работают с контекстом.
Доступ к контексту можно получить разными способами. Существуют такие методы как getApplicationContext(), getContext(), getBaseContext() или this, который упоминался выше, если используется в активности.
На первых порах не обязательно понимать, зачем он нужен. Достаточно помнить о методах, которые позволяют получить контекст и использовать их в случае необходимости, когда какой-нибудь метод или конструктор будет требовать объект Context в своих параметрах.
В свою очередь Context имеет свои методы, позволяющие получать доступ к ресурсам и другим объектам.
- getAssets()
- getResources()
- getPackageManager()
- getString()
- getSharedPrefsFile()
Возьмём к примеру метод getAssets(). Ваше приложение может иметь ресурсы в папке assets вашего проекта. Чтобы получить доступ к данным ресурсам, приложение использует механизм контекста, который и отвечает доступность ресурсов для тех, кто запрашивает доступ — активность, служба и т.д. Аналогично происходит с методом getResources. Например, чтобы получить доступ к ресурсу цвета используется конструкция getResources().getColor(), которая может получить доступ к данным из файла res/colors.xml.
Таким образом, создавая, например, вторую активность, мы можем сразу обеспечить ей доступ к своим ресурсам, так как активность относится к контексту. При создании собственных компонентов View также используется контекст в конструкторах, так как компонент тоже может использовать ваши ресурсы. При создании собственных классов, если вам нужно будет обращаться к контексту, то необходимо создать конструктор:
Через контекст можно узнать практически всю информацию о вашем приложении — имя пакета, класса и т.п.
Тем не менее, следует различать контекст в разных ситуациях. Допустим, у вас есть приложение с несколькими активностями. В манифесте можно прописать используемую тему как для всего приложения, так и для каждой активности в отдельности. Соответственно, выбор контекста повлияет на результат. Как правило, при использовании собственной темы предпочтительнее использовать контекст активности, а не приложения.
Очень часто начинающие программисты впадают в ступор, когда ключевое слово this не работает в анонимных классах, например, при щелчке кнопки. В этом случае, используйте полное имя класса перед ним.
При создании адаптеров для списков также обращаются к контексту.
Или ещё пример для адаптера в фрагменте ListFragment:
Здесь тоже следует быть внимательным, если используется своя тема для списка.
Последнее замечание относится к опытным программистам. Неправильный контекст может послужить источником утечки памяти. Если вы создадите собственный класс, в котором содержится статическая переменная, обращающая к контексту активности, то система будет держать ссылку на переменную. Если активность будет закрыта, то сборщик мусора не сможет очистить память от переменной и самой неиспользуемой активности. В таких случаях лучше использовать контекст приложения через метод getApplicationContext().
ContextCompat
В библиотеки совместимости появился свой класс для контекста ContextCompat. Он может вам пригодиться, когда студия вдруг подчеркнёт метод в старом проекте и объявит его устаревшим.
Допустим, мы хотим поменять цвет текста на кнопки.
Студия ругается, что нужно использовать новый вариант getColor(int, Theme). Заменим строчку.
Если посмотреть на исходники этого варианта, то увидим, что там тоже идёт вызов нового метода. Поэтому можно сразу использовать правильный вариант, если вы пишете под Marshmallow и выше.
Контекст вызова
Контекст — это значение ключевого слова this , которое является ссылкой на объект, который «владеет» текущим исполняемым кодом или функцией, на которую он смотрит.

Мы знаем, что window является глобальным объектом в браузере, поэтому, если мы введем его в консоли, он должен вернуть объект окна, что он и сделает.
ℹ️ Примечание
значение ключевого слова this зависит от объекта, на котором функция запускается / вызывается / работает. Поэтому ключевое слово this имеет разные значения в зависимости от того, где оно используется.
ℹ️ Примечание
this и context взаимозаменяемы.
Контекст — глобально и внутри функции.
foo — это функция, определенная на глобальном уровне и вызываемая на объекте глобального уровня, то есть window , поэтому вызов foo и window.foo одинаков. Следовательно, контекст — это объект window .
Если мы вызовем foo с помощью ключевого слова new
т.e new foo () на глобальном уровне, то получим это как объект foo .

ℹ️ Примечание
Оператор new создает экземпляр объекта. Контекст функции будет установлен на созданный экземпляр объекта.
Контекст — для функций 2-го уровня
Неявное связывание (Implicit Binding)
Когда функция определяется глобально и используется в рамках объекта.
Из вышеописанного мы получаем, что значение this зависит от вызываемой функции, а не от того, где функция определена.
Как контекст ведет себя в «use strict»?
в функции контекст, т.e this , ведет себя иначе. Контекст остается тем, к чему он был призван.
Как контекст ведет себя в контексте выполнения (execution context)?
Контекст выполнения — это «среда» или область действия, в которой выполняется функция. Каждый раз, когда вызывается функция, создается новый контекст выполнения. Каждый вызов контекста выполнения имеет 2 этапа
- Создание — при вызове функции
- Активация — при выполнении функции
Значение this определяется на этапе создания, а не во время выполнения. Однако правило определения this остается прежним.
Чем контекст отличается от области видимости?
Области видимости и контекст — это совершенно разные концепции, но обычно они используются как взаимозаменяемые. Область видимости — это доступность переменных, функций или объектов в определенной части вашего кода во время выполнения. Подробнее об области видимости здесь.
ℹ️ Важно
Каждый вызов функции имеет как область видимости, так и связанный с ним контекст.
Как явно изменить контекст? (explicitly binding)
Мы можем динамически изменять контекст любого метода, используя методы call , apply и bind .
Call — первый аргумент, который принимает call , — это контекст, который вы хотите использовать. После этого вы можете передать любое количество значений, разделенных запятыми.
Apply — то же самое, что и call , но фундаментальное различие между ними заключается в том, что функция call() принимает список аргументов, в то время как функция apply() принимает единичный массив аргументов.
Bind — возвращает новую функцию, которая навсегда привязана к первому аргументу bind , независимо от того, как функция используется. bind не вызывает связанную функцию сразу, а возвращает новую функцию, которую мы можем запустить позже.
❗️ Важно
call / apply вызывает функцию с заданным контекстом, а bind , создаёт "обёртку" над функцией, которая подменяет контекст этой функции.
bind — не вызывает функцию, а лишь возвращает "обёртку", которую можно вызвать позже. call / apply — вызывает функцию сразу и возвращает ее результат.
call / apply — позднее связывание, bind — раннее связывание.
Зачем нам нужно явно менять контекст?
Когда нам нужно вызвать функцию, определенную внутри объекта, скажем, x , но на других объектах,скажем y , используя явные методы привязки контекста для этого, чтобы увеличить возможность повторного использования функций.
— это еще одна часть, в которой используется явное изменение контекста.
Чтобы сделать служебные функции вроде
-
— это еще одно место, где можно использовать явное изменение контекста.
В каких случаях нам нужно учитывать контекст?
Мы можем потерять контекст, т.е. получить для него неопределенное значение.
- Вложенные функции
Результат:

Нам нужно сохранить контекст объекта obj , на который ссылается функция callback , когда вызывается как описано выше.тк не происходит, и мы получаем ошибку. Мы можем избавиться от указанной выше ошибки, заменив код exec на приведенный ниже
Важно
Любая ссылка на функцию (присвоение значения, передача в качестве аргумента) теряет исходную привязку функции.
Что такое контекст? Более обширный взгляд
Первый параметр статического метода makeText класса Toast — это контекст. Но я могу передать this (если вызов метода находится не дальше одного блока кода) или MainActivity.this (если имя класса-активности MainActivity ), могу также передать getApplicationContext() .
- Так в чем разница?
- Почему в качестве контекста можно передать this ? Это же ссылка на класс
- Есть ли случаи когда надо передать именно getApplicationContext ?
- Почему контекст нужен везде, где происходит работа с интерфейсом?
- Почему любой виджет имеет конструктор, в который надо передать контекст?
- Что вообще такое контекст?
Последний вопрос я задал, так как по ходу написания остальных, я понял, что ничего о нем не знаю 🙂
класс Context содержит в себе всевозможную информацию о ресурсах системы, как уже было сказано в другом ответе. Конкретно в этом вопросе нас интересует, что он содержит, помимо прочего, и параметры темы (стилей) для отображения View
Почему в качестве контекста можно передать this? Это же ссылка на класс
Активити является наследником класса Context и несет в себе информацию о контексте для этой активити, поэтому мы можем использовать ссылку на именно этот класс в качестве контекста. С Fragment , к примеру, это уже не работает — он не наследуется от Context
Есть ли случаи когда надо передать именно getApplicationContext?
Тема (стиль) всего приложения и конкретной активити может отличаться (для активити в манифесте указан другой стиль). Тогда запрос контекста приложения и контекста активти вернет разное оформление View
Почему контекст нужен везде, где происходит работа с интерфейсом?
Потому что он содержит стиль для View
на остальные вопросы ответ тот же — в контексте содержится информация, как должен выглядеть View . Например, стиль кнопки темы Holo и темы AppCompat сильно отличается, в контексте и содержится эта информация.
Возможно в приложении могут существовать и какие то другие отличия в окружении, назначенном всему приложению и конкретной активити, тогда обращение к контексту приложения или активити тоже будет иметь значение, но мне такие отличия (кроме тем и стилей) припомнить не удалось.
UPD несколько важных замечаний по getApplicationContext() , не связанных с UI приложения из этой статьи
контекст приложения следует использовать везде, где контекст необходимо передать за пределы жизненного цикла передающего компонента (в объекты, которые будут жить дольше, чем создавшая/вызвавшая их активность, например) во избежании удержания ссылки на этот компонент при использовании его собственного контекста и утечек памяти.
во внешние библиотеки следует передавать контекст приложения по тем же причинам, что и п.1
контекст приложения не имеет информации по особенностям GUI отдельной активити, если они отличаются от параметров всего приложения, в таких случаях нельзя использовать контекст приложения при работе с GUI этой активити.
приложение (класс Application ) — синглтон и его контекст тоже синглтон, этот контекст может удерживать объекты с более коротким жизненным циклом и приводить к утечкам памяти, если не позаботиться о их корректной обработке GC