Вызов функций и стек контекстов выполнения
Теперь более детально рассмотрим, что происходит с контекстом выполнения при обработке функций, как он формируется при их вызове, особенно если они вложены друг в друга, и как меняется действующее Лексическое окружение.
Рассмотрим изменение стека контекстов выполнения и соответствующих Лексических окружений на следующем примере:
var phrase = "привет!";
function sayHi(name) {
var surname = "Петров";
alert(name + " " + surname + ", " + phrase);
}
sayHi("Иван");
phrase = "пока!";
console.log("Окончание работы программы, " + phrase");
Выполнение которой можно разделить на следующие этапы:
-
До выполнения первой строчки её кода, на стадии инициализации, JavaScript-движок помещает в поле
outer
значение свойства[[Environment]]
, то естьnull
, так как в данный момент выполняется код глобальной области, у которого нет родительской области. А также создает пустой объект Записи окруженияenvironmentRecord
и сохраняет в нём имена переменных и функций, которые объявлены в данной области видимости. В данном случае туда попадает функцияsayHi
и единственная переменнаяphrase
. Здесь важно отметить то, что до выполнения кода, в запись окружения не попадают никакие значения переменных, а только лишь выделяется для них место в памяти (происходит всплытие переменных и функций). Поэтому изначально их значения установятся какundefined
, то есть “неопределено”.// globalEnvironment // outer = null // environmentRecord = { phrase: undefined, sayHi: function sayHi(name){...}} var phrase = "привет!"; function sayHi(name) { var surname = "Петров"; alert(name + " " + surname + ", " + phrase); } sayHi("Иван"); phrase = "пока!"; console.log("Окончание работы программы, " + phrase");
Изначально этот код обрабатывается в рамках глобального контекста выполнения
-
Код начинает выполняться и во время выполнения
var phrase = "привет!";
происходит присвоение нового значения глобальной переменнойphrase
(к которой можно также обратиться черезwindow.phrase
).// globalEnvironment // outer = null // environmentRecord = { phrase: "привет!", sayHi: function sayHi(name){...}} var phrase = "привет!"; // <-- выполнение этой строки изменило phrase в environmentRecord function sayHi(name) { var surname = "Петров"; alert(name + " " + surname + ", " + phrase); } sayHi("Иван"); phrase = "пока!"; console.log("Окончание работы программы, " + phrase");
-
Далее на строке
sayHi("Иван");
происходит вызов функции. Создаётся новый контекст выполнения, который помещается наверх стека контекстов. С каждым изменением контекста выполнения меняется и текущее/действующее Лексическое окружение. До захода в функцию, код выполнялся в рамках Глобального контекста, который указывал на Глобальное Лексическое окружение, а теперь Глобальный контекст приостановил своё выполнение и активным контекстом стал контекст выполнения функцииsayHi
.Действующее Лексическое окружение изменилось. Контекст выполнения
sayHi
содержит в себе указатель на текущее именно для этой функции Лексическое окружение. Для него полеouter
указывает на родительское окружениеglobalEnvironment
, и опять же, еще до выполнения первой строчки этой функции, на стадии инициализации, JavaScript-движок создает соответствующий объект Записи окружения и заполняет его. В данном случае туда попадает аргументname
и единственная переменнаяsurname
, значение которое сначала будетundefined
.// globalEnvironment // outer = null // environmentRecord = { phrase: "привет!", sayHi: function sayHi(name){...}} var phrase = "привет!"; function sayHi(name) { // sayHiEnvironment // outer = globalEnvironment // environmentRecord = { name: "Иван", surname: undefined} var surname = "Петров"; alert(name + " " + surname + ", " + phrase); } sayHi("Иван"); phrase = "пока!"; console.log("Окончание работы программы, " + phrase");
-
Функция
sayHi
начинает выполняться и во время выполненияvar surname = "Петров";
происходит присвоение нового значения локальной переменнойsurname
// globalEnvironment // outer = null // environmentRecord = { phrase: "привет!", sayHi: function sayHi(name){...}} var phrase = "привет!"; function sayHi(name) { // sayHiEnvironment // outer = globalEnvironment // environmentRecord = { name: "Иван", surname: "Петров"} var surname = "Петров"; // <-- выполнение этой строки изменило surname в environmentRecord alert(name + " " + surname + ", " + phrase); } sayHi("Иван"); phrase = "пока!"; console.log("Окончание работы программы, " + phrase");
-
На строке
alert(name + " " + surname + ", " + phrase);
JavaScript-движок сначала пытается найти необходимую переменную в записи
environmentRecord
текущего Лексического окружения, где будут найденыname
иsurname
. Если необходимой переменной в Записи текущего Лексического окружения нет, как переменнойphrase
, то поиск продолжается во внешнем окружении. В данном случае для функцииsayHi
есть только одно внешнее окружение — Глобальное окружение, в котором и будет найдена переменнаяphrase
.Такой порядок поиска возможен благодаря тому, что ссылка на внешний объект переменных хранится в поле
outer
, который в свою очередь устанавливается из внутреннего свойства функции -[[Environment]]
. Эти свойства закрыты от прямого доступа, но знание о них очень важно для понимания того, как работает JavaScript. -
По завершению функции
sayHi
её контекст выполнения удаляется из стека, активным контекстом выполнения снова становится Глобальный контекст и выполнение перейдет на следующую за вызовом функции инструкциюphrase = "пока!";
. При этом объект Записи окружения удаляется, и память очищается (случаи, когда запись окружения сохраняется и после завершения функции будут рассмотрены в следующих частях курса).// globalEnvironment // outer = null // environmentRecord = { phrase: "привет!", sayHi: function sayHi(name){...}} var phrase = "привет!"; function sayHi(name) { var surname = "Петров"; alert(name + " " + surname + ", " + phrase); } sayHi("Иван"); phrase = "пока!"; // <-- начнётся выполнение этой инструкции console.log("Окончание работы программы, " + phrase);
-
Выполнение инструкции
phrase = "пока!";
изменит значение переменной, которое в последствии и будет выведено в консоли в конце программы.// globalEnvironment // outer = null // environmentRecord = { phrase: "пока!", sayHi: function sayHi(name){...}} var phrase = "привет!"; function sayHi(name) { var surname = "Петров"; alert(name + " " + surname + ", " + phrase); } sayHi("Иван"); phrase = "пока!"; // <-- выполнение этой строки изменило phrase в environmentRecord console.log("Окончание работы программы, " + phrase); // Окончание работы программы, пока!
Теперь рассмотрим пример с большей вложенностью:
var value = 1;
function inner() {
var value;
console.log(value);
}
function outer() {
var value = 2;
console.log(value);
inner();
}
console.log(value);
outer();
console.log(value);
Для которого изменение стека контекстов выполнения и соответствующих ему лексических окружений можно показать так:
И соответственно в консоли будут выведены следующие значения:
> 1
> 2
> undefined
> 1
Оставить комментарий