- PVSM.RU - https://www.pvsm.ru -
Если вы — JavaScript-разработчик или хотите им стать, это значит, что вам нужно разбираться во внутренних механизмах выполнения JS-кода. В частности, понимание того, что такое контекст выполнения и стек вызовов, совершенно необходимо для освоения других концепций JavaScript, таких, как поднятие переменных, области видимости, замыкания. Материал, перевод которого мы сегодня публикуем, посвящён контексту выполнения и стеку вызовов в JavaScript.
Контекст выполнения (execution context) — это, если говорить упрощённо, концепция, описывающая окружение, в котором производится выполнение кода на JavaScript. Код всегда выполняется внутри некоего контекста.
В JavaScript существует три типа контекстов выполнения:
window
, и тем, что ключевое слово this
указывает на этот глобальный объект. В программе может быть лишь один глобальный контекст.eval
. Код, выполняемый внутри функции eval
, также имеет собственный контекст выполнения. Однако функцией eval
пользуются очень редко, поэтому здесь мы об этом контексте выполнения говорить не будем.Стек выполнения (execution stack), который ещё называют стеком вызовов (call stack), это LIFO-стек, который используется для хранения контекстов выполнения, создаваемых в ходе работы кода.
Когда JS-движок начинает обрабатывать скрипт, движок создаёт глобальный контекст выполнения и помещает его в текущий стек. При обнаружении команды вызова функции движок создаёт новый контекст выполнения для этой функции и помещает его в верхнюю часть стека.
Движок выполняет функцию, контекст выполнения которой находится в верхней части стека. Когда работа функции завершается, её контекст извлекается из стека и управление передаётся тому контексту, который находится в предыдущем элементе стека.
Изучим эту идею с помощью следующего примера:
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
Вот как будет меняться стек вызовов при выполнении этого кода.
Состояние стека вызовов
Когда вышеприведённый код загружается в браузер, JavaScript-движок создаёт глобальный контекст выполнения и помещает его в текущий стек вызовов. При выполнении вызова функции first()
движок создаёт для этой функции новый контекст и помещает его в верхнюю часть стека.
При вызове функции second()
из функции first()
для этой функции создаётся новый контекст выполнения и так же помещается в стек. После того, как функция second()
завершает работу, её контекст извлекается из стека и управление передаётся контексту выполнения, находящемуся в стеке под ним, то есть, контексту функции first()
.
Когда функция first()
завершает работу, её контекст извлекается из стека и управление передаётся глобальному контексту. После того, как весь код оказывается выполненным, движок извлекает глобальный контекст выполнения из текущего стека.
До сих пор мы говорили о том, как JS-движок управляет контекстами выполнения. Теперь поговорим о том, как контексты выполнения создаются, и о том, что с ними происходит после создания. В частности, речь идёт о стадии создания контекста выполнения и о стадии выполнения кода.
Перед выполнением JavaScript-кода создаётся контекст выполнения. В процессе его создания выполняются три действия:
this
и осуществляется привязка this
(this binding).LexicalEnvironment
(лексическое окружение).VariableEnvironment
(окружение переменных).Концептуально контекст выполнения можно представить так:
ExecutionContext = {
ThisBinding = <this value>,
LexicalEnvironment = { ... },
VariableEnvironment = { ... },
}
В глобальном контексте выполнения this
содержит ссылку на глобальный объект (как уже было сказано, в браузере это объект window
).
В контексте выполнения функции значение this
зависит от того, как именно была вызвана функция. Если она вызвана в виде метода объекта, тогда значение this
привязано к этому объекту. В других случаях this
привязывается к глобальному объекту или устанавливается в undefined
(в строгом режиме). Рассмотрим пример:
let foo = {
baz: function() {
console.log(this);
}
}
foo.baz(); // 'this' указывает на объект 'foo', так как функция 'baz' была вызвана
// как метод объекта 'foo'
let bar = foo.baz;
bar(); // 'this' указывает на глобальный объект window, так как при вызове функции
// ссылка на объект не используется
В соответствии со спецификацией [2] ES6, лексическое окружение (Lexical Environment) — это термин, который используется для определения связи между идентификаторами и отдельными переменными и функциями на основе структуры лексической вложенности ECMAScript-кода. Лексическое окружение состоит из записи окружения (Environment Record) и ссылки на внешнее лексическое окружение, которая может принимать значение null
.
Проще говоря, лексическое окружение — это структура, которая хранит сведения о соответствии идентификаторов и переменных. Под «идентификатором» здесь понимается имя переменной или функции, а под «переменной» — ссылка на конкретный объект (в том числе — на функцию) или примитивное значение.
В лексическом окружении имеется два компонента:
Существует два типа лексических окружений:
null
. В глобальном окружении (в записи окружения) доступны встроенные сущности языка (такие, как Object
, Array
, и так далее), которые связаны с глобальным объектом, там же находятся и глобальные переменные, определённые пользователем. Значение this
в этом окружении указывает на глобальный объект.Существует два типа записей окружения:
В результате, в глобальном окружении запись окружения представлена объектной записью окружения, а в окружении функции — декларативной записью окружения.
Обратите внимание на то, что в окружении функции декларативная запись окружения, кроме того, содержит объект arguments
, который хранит соответствия между индексами и значениями аргументов, переданных функции, и сведения о количестве таких аргументов.
Лексическое окружение можно представить в виде следующего псевдокода:
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Данные о привязках для идентификаторов
}
outer: <null>
}
}
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Данные о привязках для идентификаторов
}
outer: <Ссылка на глобальное окружение или на окружение внешней функции>
}
}
Окружение переменных (Variable Environment) — это тоже лексическое окружение, запись окружения которого хранит привязки, созданные посредством команд объявления переменных (VariableStatement
) в текущем контексте выполнения.
Так как окружение переменных также является лексическим окружением, оно обладает всеми вышеописанными свойствами лексического окружения.
В ES6 существует одно различие между компонентами LexicalEnvironment
и VariableEnvironment
. Оно заключается в том, что первое используется для хранения объявлений функций и переменных, объявленных с помощью ключевых слов let
и const
, а второе — только для хранения привязок переменных, объявленных с использованием ключевого слова var
.
Рассмотрим примеры, иллюстрирующие то, что мы только что обсудили:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
Схематичное представление контекста выполнения для этого кода будет выглядеть так:
GlobalExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Данные о привязках для идентификаторов
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Данные о привязках для идентификаторов
c: undefined,
}
outer: <null>
}
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Данные о привязках для идентификаторов
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Данные о привязках для идентификаторов
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
Как вы, вероятно, заметили, переменные и константы, объявленные с помощью ключевых слов let
и const
, не имеют связанных с ними значений, а переменным, объявленным с помощью ключевого слова var
, назначено значение undefined
.
Это так из-за того, что во время создания контекста в коде осуществляется поиск объявлений переменных и функций, при этом объявления функций целиком хранятся в окружении. Значения переменных, при использовании var
, устанавливаются в undefined
, а при использовании let
или const
остаются неинициализированными.
Именно поэтому можно получить доступ к переменным, объявленным с помощью var
, до их объявления (хотя они и будут иметь значение undefined
), но, при попытке доступа к переменным или константам, объявленным с помощью let
и const
, выполняемой до их объявления, возникает ошибка.
Только что мы только что описали, называется «поднятием переменных» (Hoisting). Объявления переменных «поднимаются» в верхнюю часть их лексической области видимости до выполнения операций присвоения им каких-либо значений.
Это, пожалуй, самая простая часть данного материала. На этой стадии выполняется присвоение значений переменным и осуществляется выполнение кода.
Обратите внимание на то, что если в процессе выполнения кода JS-движок не сможет найти в месте объявления значение переменной, объявленной с помощью ключевого слова let
, он присвоит этой переменной значение undefined
.
Только что мы обсудили внутренние механизмы выполнения JavaScript-кода. Хотя для того, чтобы быть очень хорошим JS-разработчиком, знать всё это и не обязательно, если у вас имеется некоторое понимание вышеописанных концепций, это поможет вам лучше и глубже разобраться с другими механизмами языка, с такими, как поднятие переменных, области видимости, замыкания.
Уважаемые читатели! Как вы думаете, о чём ещё, помимо контекста выполнения и стека вызовов, полезно знать JavaScript-разработчикам?
Автор: ru_vds
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/291424
Ссылки в тексте:
[1] Image: https://habr.com/company/ruvds/blog/422089/
[2] спецификацией: http://ecma-international.org/ecma-262/6.0/#sec-lexical-environments
[3] Источник: https://habr.com/post/422089/?utm_campaign=422089
Нажмите здесь для печати.