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

Рассмотрим изменение стека контекстов выполнения и соответствующих Лексических окружений на следующем примере:

var phrase = "привет!";

function sayHi(name) {
 var surname = "Петров";

 alert(name + " " + surname + ", " + phrase);
}

sayHi("Иван");

phrase = "пока!";

console.log("Окончание работы программы, " + phrase");

Выполнение которой можно разделить на следующие этапы:

  1. До выполнения первой строчки её кода, на стадии инициализации, 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");
    

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

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

  2. Код начинает выполняться и во время выполнения 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");
    
  3. Далее на строке 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");
    

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

  4. Функция 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");
    
  5. На строке

    alert(name + " " + surname + ", " + phrase);
    

    JavaScript-движок сначала пытается найти необходимую переменную в записи environmentRecord текущего Лексического окружения, где будут найдены name и surname. Если необходимой переменной в Записи текущего Лексического окружения нет, как переменной phrase, то поиск продолжается во внешнем окружении. В данном случае для функции sayHi есть только одно внешнее окружение — Глобальное окружение, в котором и будет найдена переменная phrase.

    Такой порядок поиска возможен благодаря тому, что ссылка на внешний объект переменных хранится в поле outer, который в свою очередь устанавливается из внутреннего свойства функции - [[Environment]]. Эти свойства закрыты от прямого доступа, но знание о них очень важно для понимания того, как работает JavaScript.

  6. По завершению функции 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);
    

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

  7. Выполнение инструкции 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
Обнаружили ошибку или хотите добавить что-то своё в документацию? Отредактируйте эту страницу на GitHub!

Оставить комментарий