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

Контекст выполнения (execution context) - это окружение, в котором выполняется код.

Есть три типа контекстов функции

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

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

  • Контекст выполнения функции eval. Код, выполняемый внутри функции eval, также имеет собственный контекст выполнения. Однако функцией eval пользуются очень редко, поэтому здесь мы об этом контексте выполнения говорить не будем.

Стек вызовов (call stack) или стек выполнения (execution stack) - стек для хранения контекстов вызованных функций.

Стек - это структура данных, в которой элементы упорядочены так, что последний элемент, который попадает в стек, выходит из него первым (LIFO: last in, first out). Стек похож на стопку книг: та книга, которую мы кладём последней, находится сверху.

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

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

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

  1. Определяется значение this и осуществляется привязка this (this binding).

  2. Создаётся компонент LexicalEnvironment (лексическое окружение).

  3. Создаётся компонент VariableEnvironment (окружение переменных).

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

ExecutionContext = {
  ThisBinding = <this value>,
  LexicalEnvironment = { ... },
  VariableEnvironment = { ... },
}

Жизненный цикл контекста имеет два этапа - этап создания и этап выполнения.

Привязка this

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

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

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

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

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

  1. Environment Record – объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение this).

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

"Переменная" – это просто свойство специального внутреннего объекта: Environment Record. «Получить или изменить переменную», означает, «получить или изменить свойство этого объекта».

Именно из-за формирования записи окружения и выделения памяти под переменные до выполнения кода к ним можно обращаться до их объявления в программе. Такое поведения называется **“всплытие”**или hoisting

Однако, важно обратить внимание на то, что для переменных let и const есть некоторые отличия от var переменных в механизме всплытия. Например, если обратиться к переменным let и const до их объявления, то возникнет ошибка ReferenceError, в отличие от переменной var, значение которой в таком случае отобразится как undefined.

Такая ошибка ReferenceError из-за попытки получить или установить значение let или const переменной до её объявления называется ошибкой “Временной мертвой зоны” (Temporal Dead Zone (TDZ) error).

Временная мертвая зона заканчивается именно тогда, когда само выполнение кода доберется до объявления переменной.

console.log(typeof foo); // Uncaught ReferenceError: foo is not defined
console.log(typeof aVariableThatDoesNotExist); // undefined
let foo;

В случае с необъявленной переменной aVariableThatDoesNotExist, которой не существует, оператор покажет undefined. А в случае с объявленной foo возникнет ошибка, так как эта переменная объявлена, но запрошена во время действия временной мертвой зоны.

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

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

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

Блоки кода

Мы также можем использовать «простые» блоки кода {...}, чтобы изолировать переменные в «локальной области видимости».

{
    let foo = 'foo'

    // foo
    console.log(foo)
}

// здесь также будет ошибка
console.log(foo)

{
    let foo = 'foo'
}

IIFE

В прошлом в JavaScript не было лексического окружения на уровне блоков кода.

Так что программистам пришлось что-то придумать. И то, что они сделали, называется «immediately-invoked function expressions» (аббревиатура IIFE), что означает функцию, запускаемую сразу после объявления.

Это не то, что мы должны использовать сегодня, но, так как вы можете встретить это в старых скриптах, полезно понимать принцип работы.

(function() {

  let message = "Hello";

  alert(message); // Hello

})();

Подробнее: https://learn.javascript.ru/closure#iife.

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

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

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

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

Объект arguments

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

Можно обращаться в параметрам по их индексу, начиная с нуля. Также у объекта есть свойство length, которое позволяет определить количество аргументов.

function () {
    console.log(arguments)
}

Начиная с ES6 более предпочтительным споособом по сравнению с arguments является синтаксис rest parameters:

function (...args) {
    console.log(args)
}

Замыкание

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

let name = "John";

function sayHi() {
  alert("Hi, " + name);
}

name = "Pete";

// выведет "Hi, Pete"
sayHi();

Источники

Last updated