Контекст выполнения функции в JavaScript
Контекст выполнения функции — это одно из фундаментальных понятий в JavaScript. Контекстом еще часто называют значение переменной this внутри функции. Также иногда путают понятия «контекст выполнения» и «область видимости» — это не одно и то же. Давайте разберемся с этими понятиями.
Каждый вызов функции имеет и область видимости, и переменную this, и контекст выполнения. Область видимости определяет доступ к переменным при вызове функции и является уникальной для каждого вызова. Значение переменной this — это ссылка на объект, который «вызывает» код в данный момент. Контекст выполнения содержит и область видимости, и аргументы функции, и переменную this.
Переменная this
Значение переменной this чаще всего определяется тем, как вызывается функция. Когда функция вызывается как метод объекта, переменная this приобретает значение ссылки на объект, который вызывает этот метод:
var user = {
name: 'John Smith',
getName: function() {
console.log(this.name);
}
};
user.getName(); // John Smith
Тот же принцип применяется при вызове функции с оператором new, чтобы создать экземпляр объекта. При вызове таким образом, в качестве значения this в рамках функции будет установлена ссылка на вновь созданный объект, например:
function test(){
alert(this);
}
test(); // window
new test(); // test
Когда мы вызываем функцию как функцию (не как метод объекта), эта функция будет выполнена в глобальном контексте. Значением переменной this в данном случае будет ссылка на глобальный объект. Однако, если функция вызывается как функция в строгом режиме (strict mode) — значением this будет undefined.
Контекст выполнения
Код в JavaScript может быть одного из следующих типов:
- eval-код — код, выполняющийся внутри функции eval();
- код функции — код, выполняющийся в теле функции;
- глобальный код — код, не выполняющийся в рамках какой-либо функции.
Когда интерпретатор JavaScript выполняет код, по умолчанию контекстом выполнения является глобальный контекст. Каждый вызов функции приводит к созданию нового контекста выполнения.
Рассмотрим пример:
var hello = 'Hello';
var user = function() { // контекст выполнения функции
var name = 'John Smith';
var getName = function() { // контекст выполнения функции
return name;
}
var sayHello = function() { // контекст выполнения функции
console.log(hello + ', ' + getName());
}
sayHello();
}
user();
В данном примере мы имеем один глобальный контекст выполнения и 3 контекста выполнения функции.
Каждый раз, когда создается новый контекст выполнения, он добавляется в верхнюю часть стека выполнения. Браузер всегда будет выполнять код в текущем контексте выполнения, который находится на вершине стека выполнения. После завершения, контекст будет удален из верхней части стека и управление вернется к контексту выполнения ниже.
Основные вещи, которые необходимо помнить и понимать о контексте выполнения:
- Однопоточность — JavaScript работает в однопоточном режиме, т.е. только одна операция может быть выполнена в определенный момент времени.
- Синхронное выполнение кода — код выполняется синхронно, т.е. следующая операция не выполняется до завершения предыдущей.
- Один глобальный контекст выполнения.
- Бесконечное количество контекстов выполнения функции.
- Каждый вызов функции создает новый контекст выполнения, даже если функция рекурсивно вызывает сама себя.
В интерпретаторе JavaScript каждое создание контекста выполнения происходит в два этапа: этап создания (когда функция только вызвана, но код внутри нее еще не выполняется) и этап выполнения. На этапе создания интерпретатор сначала создает объект переменных (также называемый объект активации), который состоит из всех переменных, объявлений функций и аргументов, определенных внутри контекста выполнения. Затем инициализируется область видимости, и в последнюю очередь определяется значение переменной this. На этапе выполнения внутренним переменным присваивается значение, код интерпретируется и выполняется.
Таким образом, контекст выполнения функции можно представить в виде следующего объекта:
executionContextObj = {
variableObject: { /* объект активации - состоит из параметров функции, внутренних переменных и объявлений функций */ },
scopeChain: { /* цепочка областей видимости - объект активации + все объекты активации родительских контекстов выполнения */ },
this: {}
}
Для каждого контекста выполнения существует своя цепочка областей видимости. Цепочка областей видимости контекста выполнения включает области видимости из предыдущих контекстов в стеке выполнения.
Т.е. каждый раз, когда мы пытаемся получить доступ к переменной в контексте выполнения функции, процесс поиска этой переменной начинается с собственной области видимости функции. Если переменная с таким именем в текущей области видимости не найдена, поиск продолжается в иерархии областей видимости.
Понятия области видимости и контекста выполнения очень важны и играют значительную роль в языке JavaScript. Их хорошее понимание важно для изучения ряда шаблонов проектирования, понимания работы замыканий, функций обратного вызова, частичного применения функций и других важных концепций JavaScript.