Тонкости ECMA-262-3. Часть 1. Контексты исполнения.

Read this article in: English, Chinese, Arabic.

В этой заметке мы затронем контексты исполнения JavaScript и связанные с ними типы исполняемого кода.

Итак, каждый раз, когда происходит передача управления исполняемому коду ECMAScript-программы, осуществляется вход в контекст исполнения.

Контекст исполнения (Execution context, сокращённо – EC) – это абстрактное понятие, используемое спецификацией ECMA, для типизации и разграничения исполняемого кода.

Стандарт не задаёт чётких рамок на структуру и вид EC с точки зрения реализации; это – задача JavaScript-движков, реализующих стандарт.

Логически, совокупность контекстов исполнения представляет собой стек. Дно этого стека – всегда глобальный контекст, верхушка – текущий (активный) контекст исполнения. Стек модифицируется (наполняется/очищается) по мере входа в различные виды EC.

С абстрактным понятием контекста исполнения, связано понятие типа исполняемого кода. Говоря о типе кода, можно, в определённых моментах, подразумевать контекст исполнения.

Для примеров, обозначим стек контекстов исполнения в виде массива:

ECStack = [];

Стек наполняется каждый раз при входе в функцию (даже, если функция вызвана рекурсивно, или, в качестве конструктора), а также, при работе функции eval.

Это тип кода, обрабатываемый на уровне “Программа”: загруженный js-файл или inline-код (между тегами <script></script>). Данный тип не включает в себя тех частей кода, которые находятся в телах функций.

При инициализации (запуске программы), ECStack имеет вид:

ECStack = [
  globalContext
];

При передаче управления функции (всем видам функций), ECStack наполняется новыми элементами. Стоит отметить, что код конкретной функции не включает в себя коды вложенных функций. Для примера, возьмём функцию, вызывающую себя рекурсивно один раз:

(function foo(bar) {
  if (bar) {
    return;
  }
  foo(true);
})();

Тогда, ECStack модифицируется следующим образом:

// первый запуск foo
ECStack = [
  <foo> functionContext
  globalContext
];

// рекурсивный запуск foo
ECStack = [
  <foo> functionContext - рекурсивно
  <foo> functionContext
  globalContext
];

Каждый возврат из функции завершает текущий контекст исполнения, и ECStack модифицируется соответствующим образом: отработавшие контексты удаляются последовательно и в обратном порядке (вполне естественная организация стека). Брошенное, но не пойманное, исключение также может завершать один или более контекстов. После отработки данного кода, ECStack снова содержит лишь globalContext, — вплоть до завершения программы.

С кодом eval-а – интересней. В данном случае, присутствует понятие вызывающего контекста (calling context), т.е. контекста, из которого вызвана функция eval. Модификации, производимые eval-ом (например, объявление переменной или функции), воздействуют на вызывающий контекст:

eval('var x = 10');

(function foo() {
  eval('var y = 20');
})();

alert(x); // 10
alert(y); // "y" is not defined

Модификация ECStack:

ECStack = [
  globalContext
];

// eval('var x = 10');
ECStack.push(
  evalContext,
  callingContext: globalContext
);

// отработал eval
ECStack.pop();

// вызов функции foo
ECStack.push(<foo> functionContext);

// eval('var y = 20');
ECStack.push(
  evalContext,
  callingContext: <foo> functionContext
);

// отработал eval
ECStack.pop();

// return из foo
ECStack.pop();

Т.е. вполне обычный и логичный Call-стек.

В реализации SpiderMonkey (встроена в Firefox, Thunderbird), вплоть до версии 1.7, в функцию eval вторым параметром можно передавать вызывающий контекст. Таким образом, если контекст существует, можно воздействовать на “приватные” (как их любят называть) var-ы:

function foo() {
  var x = 1;
  return function () { alert(x); };
};

var bar = foo();

bar(); // 1

eval('x = 2', bar); // указываем контекст, воздействуем на внутренний var "x"

bar(); // 2

Данный теоретический минимум необходим для дальнейшего разбора объектов, связанных с контекстами исполнения (таких как Объект переменных (Variable object), Цепь областей видимости (Scope chain) и т.д., описание которых можно найти в соответствующих заметках).

Соответствующий раздел спецификации ECMA-262-3 — 10. Контексты исполнения.

Автор: Dmitry A. Soshnikov
Дата публикации: 26.06.2009


Tags: , , ,

 
 
 

3 Comments:

  1. Gravatar of Nekromancer Nekromancer
    24. April 2010 at 16:50

    кстати, везде кроме ИЕ можно проводить вот таки операции с контекстом при помощи eval, вызывая её для другого объекта.

    (function(){
    	eval.call(window, "var test = 'my value'");
    }());
    alert(test);

    Или даже вот такой вариант:

    var myFn = function(){
    	alert(test);
    }
    (function(){
    	eval.call(mtFn, "var test = 'my value'");
    }());

  2. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    24. April 2010 at 22:29

    @Nekromancer

    Во втором примере объявляется глобальная переменная test.

    Вы забыли поставить точку с запятой после определения первой анонимной функции (которая сохраняется в переменную myFn). В итоге, вся эта конструкция означает запуск этой первой анонимной функции, с передачей ей в качестве параметра — результата выполнения второй анонимной функции.

    Т.е.:

    var myFn = function(){
        alert(test);
    }(function(){ .... }()); // <-- вызов первой функции

    Но значением параметра для первой функции будет всё равно undefined, т.к. именно его неявно возвращает вторая функция (поскольку нет явного return‘a).

    А в момент выполнения второй функции переменной myFn ещё не присвоена первая функции, и поэтому, myFn пока равен undefined (проверьте alert‘ом перед eval‘ом во второй функции).

    Поэтому Ваш eval.call(mtFn, "var test = 'my value'"); равносилен eval.call(undefined, "var test = 'my value'");. Что равносильно (по алгоритму call) eval.call(window, "var test = 'my value'");.

    Поэтому переменная test будет доступна в глобальном контексте.

    И всё это из-за забытой точки с запятой ;)

    Dmitry.


  3. Gravatar of Vitaly Vitaly
    1. September 2010 at 01:38

    объявили myFn, а в call передаем mtFn ;)


Leave a Reply

Code: For code you can use tags [js], [text], [ruby] and other.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>