in ECMAScript

Тонкости ECMA-262-3. Часть 2. Объект переменных.

Read this article in: English, Chinese (version1, version2, version 3), Korean, German, French.

Постоянно в программах мы описываем функции и переменные, которые затем благополучно используем (вызываем, обращаемся) при построении наших систем. Однако, как и где интерпретатор находит наши данные (функции, переменные)? Что происходит, когда мы обращаемся к нужным нам объектам?

Многие знают, что переменные тесно связаны со своим контекстом исполнения:

var a = 10; // переменная глобального контекста

(function () {
  var b = 20; // локальная переменная контекста функции
})();

alert(a); // 10
alert(b); // b is not defined

Также, многим известно, что обособленную область видимости в текущей спецификации, создают лишь контексты исполнения типа “Функция”. Т.е., в отличии от C/C++, к примеру, цикл for в ECMAScript не создаёт локальный контекст:

for (var k in {a: 1, b: 2}) {
  alert(k);
}

alert(k); // переменная "k" осталась жить и после цикла

Посмотрим более детально, что происходит, когда мы объявляем наши данные.

Итак, коль скоро, переменные связаны с контекстом, последний должен знать, где хранятся его данные и как их получить. Данный механизм называется Объектом переменных.

Объект переменных (Variable object, сокращённо VO) – это связанный с контекстом исполнения объект, служащий хранилищем для:

  • переменных (var);
  • деклараций функций (FunctionDeclaration, сокращённо FD);
  • и формальных параметров функции,

объявленных в данном контексте.

Обратите внимание, в ES5 концепция объекта переменных (variable object) заменена моделью лексических окружений (lexical environments), подробное описание которых можно найти в соответствующей главе.

Схематично и для примеров, объект переменных можно представить в виде обычного JS-объекта:

VO = {};

И, как мы отметили, VO является свойством контекста исполнения:

activeExecutionContext = {
  VO: {
    // данные контекста (var, FD, параметры функций)
  }
};

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

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

Пример:

var a = 10;

function test(x) {
  var b = 20;
};

test(30);

Имеем:

// Объект переменных глобального контекста
VO(globalContext) = {
  a: 10,
  test: <ссылка на функцию>
};

// Объект переменных контекста функции test
VO(test functionContext) = {
  x: 30,
  b: 20
};

Но, и на уровне реализации (да и спецификации) объект переменных является абстрактной сущностью. Физически же, в конкретных контекстах исполнения, VO называется по-разному и имеет разную инициализирующую структуру.

Некоторое базовые операции (например, создание переменных — variable instantiation) и поведение объекта переменных являются общими для всех контекстов исполнения. Контекст функции может также определять дополнительные сущности, связанные с VO.

AbstractVO (общее поведение процесса создания переменных)

  ║
  ╠══> GlobalContextVO
  ║        (VO === this === global)
  ║
  ╚══> FunctionContextVO
           (VO === AO, добавлены: объект <arguments> и <формальные параметры>)

Рассмотрим эти случаи подробней.

Здесь, для начала, нужно дать определение Глобального объекта.

Глобальный объект (Global object) — объект, который создаётся до входа в любой из контекстов исполнения. Данный объект существует в единственном экземпляре, свойства его доступны из любого места программы, жизненный цикл объекта завершается с завершением программы.

При создании, глобальный объект инициализируется такими свойствами, как Math, String, Date, parseInt и т.д., а также, дополнительными объектами, среди которых может быть и ссылка на сам глобальный объект — например, в BOM, свойство window глобального объекта ссылается на сам глобальный объект (однако, не во всех реализациях):

global = {
  Math: <...>,
  String: <...>
  ...
  ...
  window: global
};

При обращении к свойствам глобального объекта префикс обычно не используется, поскольку сам глобальный объект не доступен напрямую по имени. Однако, получить доступ к нему можно посредством значения this в глобальном контексте, а также через свойства-ссылки на самого себя, например window в BOM, поэтому пишется просто:

String(10); // подразумевается global.String(10);

// с префиксами
window.a = 10; // === global.window.a = 10 === global.a = 10;
this.b = 20; // global.b = 20;

Так вот, возвращаясь к объекту переменных глобального контекста. В глобальном контексте, объектом переменных является сам глобальный объект:

VO(globalContext) === global;

Необходимо чётко осознавать этот факт, т.к. именно поэтому, объявляя переменную в глобальном контексте, мы имеем возможность косвенно обратиться к ней посредством свойства глобального объекта (например, когда имя переменной заранее неизвестно):

var a = new String('test');

alert(a); // напрямую, будет найдено в VO(globalContext): "test"

alert(window['a']); // косвенно через global === VO(globalContext): "test"
alert(a === this.a); // true

var aKey = 'a';
alert(window[aKey]); // косвенно, имя свойства сформировано налету: "test"

Касательно же контекста исполнения типа “Функция”, — здесь VO недоступен напрямую, и его функцию исполняет, так называемый, Объект активации.

VO(functionContext) === AO;

Объект активации (Activation object, сокращённо AO) — специальный объект, который создаётся при входе в контекст функции и инициализируется свойством argumentsОбъект аргументов (Arguments object):

AO = {
  arguments: <ArgO>
};

Объект аргументов (Arguments object, сокращённо ArgO) – объект, находящийся в объекте активации контекста функции и содержащий следующие свойства:

  • callee – ссылка на выполняемую функцию;
  • length – количество реально переданных параметров;
  • свойства-индексы (числовые, приведённые к строке), значения которых – есть формальные параметры функции (слева направо в списке параметров). Количество этих свойств-индексов == arguments.length. Значения свойств-индексов объекта arguments и присутствующие формальные параметры – взаимозаменяемы:

Пример:

function foo(x, y, z) {

  // количество описанных параметров функции (x, y, z)
  alert(foo.length); // 3
  
  // количество реально переданных параметров (только x, y)
  alert(arguments.length); // 2
  
  // ссылка функции на саму себя
  alert(arguments.callee === foo); // true

  // разделение параметров

  alert(x === arguments[0]); // true
  alert(x); // 10

  arguments[0] = 20;
  alert(x); // 20

  x = 30;
  alert(arguments[0]); // 30
  
  // однако, для не переданного параметра z,
  // соответствующее свойство-индекс объекта
  // arguments - не взаимозаменяемое

  z = 40;
  alert(arguments[2]); // undefined

  arguments[2] = 50;
  alert(z); // 40

}
 
foo(10, 20);

Относительно последнего случая, в старых версиях Google Chrome был баг — там параметр z и arguments[2] были также взаимозаменяемы.

В ES5 понятие объекта активации (activation object) также заменено единой моделью лексических окружений (lexical environments).

Сейчас мы подошли к ключевому моменту, касающемуся данной заметки. Обработка кода контекста исполнения делится на два основных этапа:

  1. Вход в контекст исполнения;
  2. Непосредственно, интерпретация кода.

С этими двумя этапами тесно связана модификация объекта переменных.

Обратите внимание, что обработка этих двух стадий является общей для всех контекстов.

При входе в контекст исполнения (но до построчного выполнения его кода), VO наполняется следующими свойствами (они уже были описаны в начале статьи):

  • для каждого формального параметра функции (если мы находимся в контексте исполнения функции)
  • – создаётся свойство VO с именем и значением формального параметра; для непереданных параметров – создаётся свойство VO с именем формального параметра и значением undefined;

  • для каждой декларации функции (FunctionDeclaration, FD)
  • – создаётся свойство VO, с именем функции и значением, являющимся ссылкой на объект-функцию; если в VO уже присутствовало свойство с таким именем, оно его значение и атрибуты заменяются значением функции;

  • для каждой переменной (var)
  • – создаётся свойство VO с именем переменной, и значением undefined; если в VO уже присутствовало свойство с таким именем, оно остаётся нетронутым.

Разберём на примере:

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}
 
test(10); // вызов

При входе в контекст функции “test” с переданным параметром 10, AO будет иметь следующий вид:

AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <ссылка на FunctionDeclaration "d">
  e: undefined
};

Обратите внимание, что в AO не попала функция “x”. Это потому, что “x” является не декларацией функции, а функцией-выражением (FunctionExpression, сокращённо FE), которые не воздействуют на VO. Однако, функция “_e” также является функцией-выражением, но, как мы увидим ниже, за счёт присваивания ссылки на неё переменной “e”, она становится доступна посредством “e”. О разнице FunctionDeclaration от FunctionExpression можно почитать в соответствующей заметке.

А далее наступает вторая фаза обработки кода — построчное выполнение.

К этому моменту, AO/VO уже наполнен свойствами (хотя, и не все из них ещё имеют истинные значения, описанные нами в коде, пока, большинство из них инициализировано значением undefined).

Рассматривая всё тот же пример, AO/VO, по мере интерпретации кода, модифицируется следующим образом:

AO['c'] = 10;
AO['e'] = <ссылка на FunctionExpression "_e">;

Ещё раз отметим, что FunctionExpression _e осталась в памяти лишь за счёт переменной e. FunctionExpression x не попала в AO/VO: т.е., если в коде попытаться вызвать функцию x до или после объявления – будет ошибка x is not defined”. Несохранённую FunctionExpression, можно вызвать лишь вместе с объявлением, либо рекурсивно.

Ещё (классический) пример:

alert(x); // function

var x = 10;
alert(x); // 10

x = 20;

function x() {}

alert(x); // 20

Почему в первом выводе “x” — функция, да ещё и доступна до объявления? Почему не 10 и не 20? Потому что, согласно правилу – VO наполняется декларациями функций ещё при входе в контекст, там же, при входе, встречается объявление переменной “x”, но переменные в VO имеют более низкий приоритет, нежели декларации функций, поэтому, при входе, заполнение VO произойдёт следующим образом:

VO = {};

VO['x'] = <ссылка на FunctionDeclaration "x">

// найдена var x = 10;
// если бы до этого не была объявлена функция
// с таким же именем, x стало бы undefined, в нашем же
// же случае переменная не затирает значение функции

VO['x'] = <значение осталось прежним - функция>

А вот уже при выполнении кода, VO модифицируется так:

VO['x'] = 10;
VO['x'] = 20;

о чём явно свидетельствуют второй и третий выводы.

В примере ниже, мы снова видим, что переменные попадают в VO ещё при входе в контекст (так, блок else никогда не выполнится, но, тем не менее, переменная “b” существует в VO):

if (true) {
  var a = 1;
} else {
  var b = 2;
}

alert(a); // 1
alert(b); // undefined, но не "b is not defined"

Часто, в различных статьях о JavaScript, можно видеть утверждения вроде: “глобальные переменные можно объявлять и с var (в глобальном контексте) и без var (в любом месте)”. Это не так. Запомните:

переменные объявляются только с ключевым словом var.

Присвоение же вроде:

a = 10;

лишь создаёт очередное свойство (но не переменную) в глобальном объекте. “Не переменную” не в том смысле, что её нельзя изменить, а “не переменную” в понятии переменных в ECMAScript (которые затем также станут свойствами глобально объекта посредством VO(globalContext) === global, помним, да?).

А разница следующая (покажем на примере):

alert(a); // undefined
alert(b); // "b" is not defined

b = 10;
var a = 20;

Всё, опять же, вытекает из VO и стадий его модификации (вход в контекст, исполнение контекста):

Вход:

VO = {
  a: undefined
};

Видим, что никакого “b” ещё нет, т.к. это не переменная, “b” появится лишь при исполнении кода (правда, в нашем случае не появится, т.к. будет ошибка). Изменим код:

alert(a); // undefined, понятно почему

b = 10;
alert(b); // 10, создалось при исполнении

var a = 20;
alert(a); // 20, модифицировалось при исполнении

Ещё один важный момент относительно переменных. Переменные, в отличии от простых свойств, получают атрибут {DontDelete}, означающий невозможность удалить свойство посредством оператора delete:

a = 10;
alert(window.a); // 10

alert(delete a); // true

alert(window.a); // undefined

var b = 20;
alert(window.b); // 20

alert(delete b); // false

alert(window.b); // по-прежнему, 20

Но, есть один контекст исполнения, на который это правило не действует, это – контекст eval: здесь {DontDelete} var-ам не выставляется:

eval('var a = 10;');
alert(window.a); // 10

alert(delete a); // true

alert(window.a); // undefined

Напоминание для тех, кто тестирует эти примеры в консоли отладчика (например, в Firebug): учитывайте, что Firebug также использует eval для запуска вашего кода. И поэтому там var-ы тоже не имеют {DontDelete}.

Как уже отмечалось, по стандарту, получить прямой доступ к объекту активации – невозможно. Однако, в некоторых реализациях, а именно в SpiderMonkey и Rhino, функциям доступно свойство __parent__, являющееся объектом активации (либо, объектом переменных), в котором данные функции были порождены.

Пример (SpiderMonkey, Rhino):

var global = this;
var a = 10;

function foo() {}

alert(foo.__parent__); // global

var VO = foo.__parent__;

alert(VO.a); // 10
alert(VO === global); // true

В примере выше видно, что функция foo порождена в глобальном контексте, и, соответственно, её свойство __parent__ указывает на объект переменных глобального контекста, т.е. на сам глобальный объект.

Однако, получить объект активации в SpiderMonkey таким образом уже не удастся: в зависимости от версии, __parent__ для внутренней функции будет возвращать либо null, либо глобальный объект.

В Rhino же, доступ к объекту активации открыт и осуществляется аналогичным способом:

Пример (Rhino):

var global = this;
var x = 10;

(function foo() {

  var y = 20;
  
  // объект активации foo
  var AO = (function () {}).__parent__;
  
  alert(AO.y); // 20

  // __parent__ текущего объекта 
  // активации - уже глобальный объект,
  // т.е. образуется цепь объектов переменных
  alert(AO.__parent__ === global); // true
  
  alert(AO.__parent__.x); // 10

})();

В этой статье мы ещё дальше продвинулись в изучении объектов, связанными с контекстами исполнения. Надеюсь, материал будет полезен, и прояснит некоторые аспекты и неясности, которые у вас, возможно, были. Далее по плану: цепь областей видимости (Scope chain), разрешение имён идентификаторов (Identifier resolution) и, как следствие, замыкания (Closures).

Если у вас возникнут вопросы, я, с удовольствием, отвечу на них в комментариях.

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

Write a Comment

Comment

32 Comments

  1. отличное объясниение спасибо

  2. Добрый день Дмитрий. Спасибо вам за эти замечательные статьи!

    Я хотел бы предложить изменить DOM на BOM, посколько Document Object Model не определяет таких объектов как Window, Screen и др., но это только предложение.

    Максим.

  3. @serega, рад, что полезно.

    @maksimr

    изменить DOM на BOM, посколько Document Object Model не определяет таких объектов как Window, Screen и др.

    Да, хорошее замечание; исправил.

  4. В JavaScript все локальные переменные и фунции являются свойствами специального объекта, который называется LexicalEnvironment (лексическое окружение).

    Правильно ли я понял, что в Вашей статье этот объект Вы назвали – АО (Activation object) .

  5. @arbeiter

    Данная статья относится к циклу ECMA-262-3, и здесь этот объект называется Variable object (или Activation object в функициях).

    В ECMA-262-5 терминология (да и семантика в некоторых местах) была несколько изменена — там уже используется модель окружений.

    Но более правильно в ES5 этот объект называется VariableEnvironment — т.к. именно он используется для наполнения переменными при входе в контекст. Однако в runtime, работа уже идет, да — c LexicalEnvironment.

    Лексические окружения подробно описаны в цикле ECMA-262-5: Chapter 3.1. Lexical environments: Common Theory и Chapter 3.2. Lexical environments: ECMAScript implementation.

  6. Дмитрий, я совсем новичок в программировании, поясните пожалуйста следующие моменты:

    1) “Глобальный объект (Global object) — … жизненный цикл объекта завершается с завершением программы.”
    Вопрос: программа завершается, когда происходит закрытие окна браузера или же когда происходит окончательная загрузка страницы(тогда как же быть с событиями)?

    2) из части 6.Замыкания.6.4.Применение замыканий:

    var a = 10;
    // только для примера
    xmlHttpRequestObject.onreadystatechange = function () {
    // callback, который вызовется отложенно,
    // когда данные будут готовы;
    // переменная “а” здесь доступна,
    // даже несмотря на то, что контекст,
    // породивший “а” уже завершился
    alert(a); // 10
    };
    ..
    Вопрос: Если контекст, породивший а, глобальный, то означает ли завершение контекста также и завершение жизни глобального объекта?

  7. @Ivan

    Вопрос: программа завершается, когда происходит закрытие окна браузера или же когда происходит окончательная загрузка страницы(тогда как же быть с событиями)?

    На самом деле я выбрал не совсем удачную формулировку. Поскольку относительно Web’a, Программой считается код каждого отдельного тега script.

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

    <script>
    // программа 1
    
    // глобальная переменная
    var a = 10;
    </script>
    
    
    <script>
    // программа 2
    
    // доступно из глобального объекта,
    // который все еще существует
    console.log(a); // 10
    </script>
    

    Программой также считается код eval‘a.

    Поэтому то, что имелось в виду в моем предложении, точнее можно перефразировать как “жизненный цикла глобального объекта заканчивается (в общем случае) с завершением интерпретатора“. В “общем случае” потому, что может быть несколько глобальных объектов (например, открыто несколько окон или iframe’ов, но при этом, возможно, используется один и тот же интерпретатор).

    Вопрос: Если контекст, породивший а, глобальный, то означает ли завершение контекста также и завершение жизни глобального объекта?

    Вот как раз из примера выше с двумя тегами script видно, что было порождено два глобальных контекста, которые используют один и тот же глобальный объект. Поэтому ответ на этот вопрос — нет, глобальный объект уничтожится, когда уничтожится интерпретатор (по сути, да — когда закроется окно браузера, если упрощенно).

  8. Дмитрий !
    В секции “Ещё (классический) пример:” добавь вызов функции x, чтобы было понятно что с ней происходит – а то возникаеют вопросы (пришлось пошагово дебажить в хроме, чтобы ответить себе на этот вопрос)

    Как-то так:

    alert(x); // function
     
    var x = 10;
    alert(x); // 10
     
    x = 20;
    
    x()
     
    function x() {}
     
    alert(x); // 20
  9. @денис

    А в твоем примере функция не вызовется. На момент активации, x будет уже числом.

  10. Дмитрий я об этом и говорю !

    Статьи просто филигранны – все четко и понятно – а ситуация с с заменой функции на число хитра и не очевидна – просто добавь свой коммент как ремарку к тексту если не трудно.

  11. Если “в глобальном контексте, объектом переменных является сам глобальный объект”, почему так:

     
    alert(this.a)  //undefined
    alert(a)  //is not defined
    

    Я думал, что при обращении к ‘потенциальной’ переменой, идет поиск названия переменной в VO, то есть в коде выше, при интерпретации alert(a) идет поиск поля 'a' в объекте this.

  12. @миша,

    Абсолютно верно, поиск идет, и переменная/свойство глобального объекта отсутствует. И это, по алгоритму поиска переменных, должно бросить ReferenceError.

    Алгоритм же прямого чтения свойств иной — тут дожно вернуться undefined, если свойство не найдено.

    Чтобы удостовериться, что поиск переменных идет и в глобальном объекте и в его цепи прототипов, можно, объявить свойство в Object.prototype (глобальный объект найследует от него, например в Firefox):

    this.a = 10;
    Object.prototype.b = 20;
    
    console.log(a, b); // 10, 20
    
  13. Дмитрий, у меня возник недавно вопрос (когда я где-то читал статью про пространство имен), вот прочитав про переменные вдруг вспомнил:
    Как известно, засорять глобальное пространство имен это плохо. Допустим на странице несколько скриптов (программ, как Вы выразились). Скажем скрипт рекламодателя и мой скрипт с обработчиком клика. Скрипт рекламодателя исполняется сразу после загрузки страницы. Мой скрипт только во время клика (правильно я думаю?). Если я объявлю переменную с именем, идентичным имени переменной из первого скрипта, произойдет ли перезапись значения переменной первого скрипта. Ведь эти две программы(скрипта) разделяют один глобальный контекст. Поясните, пожалуйста, этот момент.

  14. @Scripter

    Мой скрипт только во время клика (правильно я думаю?)

    Да, если Ваш скрипт — это полностью обработчик клика, то, конечно, он выполняется только по клику.

    Если же инициализация Вашего обработчика происходит в отдельном теге script, то этот тег и его код выполняются при загрузке странице. При этом, не обязательно, что Ваш обработчик запускается в это время — просто проходит его инициализация (однако сам скрипт может использовать вспомогательные переменные). И, если в этот момент Вы присваиваете значения глобальным переменным, они, естественно, перезаписывают старые значения (если это позволено; у переменных может быть выставлен, например, [[Writable]] в false).

    Поэтому, да, верно, обе программы разделяют один и тот же глобальный объект, и перезапись происходит.

  15. Статьи просто супер! )

    И у меня вопросик про консоль отладчика, Вы написали что они исполняют код через eval, я проверил в фаер баге – так и есть, но когда я затестил в Хроме мне не удалось удалить переменную объявленную с var. Это связано с движком V8 и другим принципом работы этого браузера?

  16. // Объект переменных глобального контекста
    VO(globalContext) = {
      a: 10,
      test: <ссылка на функцию>
    };

    Может быть точнее “код (операторы) функции” ,
    т.к. alert(test) выдает именно текст функции.

  17. Спасибо, очень хорошая статья, многое для меня разъяснила! )

  18. Дмитрий, насколько я понял если создать свойство VO в текущем контексте исполнения, а не переменную (т.е. без ключевого слова “var”), то это свойство не будет видно в порожденных из текущего контекстах? Т.е. замыкание на такие свойства не распространяется? И таким образом, когда текущий контекст будет удален из стека то и проподет это свойство вместе с ним?
    К чему я это все веду, что можно заранее продумать какие свойства нужны будут в порождаемых контекстах, а какие можно смело удалить с текущим контекстом, т.е. вовремя освобождая оперативную память.

  19. Отвечу на свой вопрос сам. Свойство так же доступно в порождённых контекстах и остаётся доступно, после удаления текущего контекста, где оно было определено, из стека.

  20. Косвенное обращение к переменным (по именам свойств VO) позволяет лишь VO глобального контекста (где сам глобальный объект и является объектом переменных, но об этом чуть позже). Для остальных контекстов напрямую обратиться к VO невозможно; это чисто механизм реализации.

    надеюсь не отвлеку понапрасну но
    наверное первое слово длжно быть “Прямое”?

    и еще одна маленькая штучка

    activeExecutionContext = {
      VO: {
        // данные контекста (var, FD, параметры функций)
      }
    };

    здесь наверно параметры функциИ а не функциЙ, один VO одна функция?

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

    Если не затруднит… о каком имени идет речь? У объектов есть имена?

  21. @Domingo

    наверное первое слово длжно быть “Прямое”?

    Нет, все-таки косвенное. Т.е. через свойства объекта переменных:

    var a = 10; // переменная
    
    console.log(a); // прямое обращение
    
    console.log(this.a); // косвенное
    var k = 'a';
    console.log(this[k]); // тоже косвенное

    здесь наверно параметры функциИ а не функциЙ, один VO одна функция?

    В данном случае несущественно, имеется в виду абстрактное понятие VO, не конкретно одной функции.

    Если не затруднит… о каком имени идет речь? У объектов есть имена?

    Нет, у объектов нет имен. Имена — у переменных и свойств, которые ссылаются на эти объекты. Глобальный объект доступен через this в глобальном коде (либо через window, но window — это уже браузерная среда). Но нет специального отдельного имени переменной для глобального объекта в спецификации. Некоторые реализации, например NodeJS, делают ссылку global, и можно писать global.a (так же как и this.a). Но обычно, если имя переменной известно, то обычно пишут просто a.

  22. Дмитрий, спасибо за ответы, даже как то неудобно отвлекать такого талантливого человека детскими вопросами. Если бы на вашем сайте была кнопка “помочь проекту” я бы ей воспользовался. Ваши статьи это лучшее что мне встречалось по стилю изложения. На мой вкус, отличный баланс ясности и лаконичности. Вы не пишете книги?

  23. @Domingo, рад, что оказалось полезным, спасибо.

    Да, по поводу книги были мысли, но все не найду времени. Посмотрим, может ES6 что-нибудь изменит в этом направлении.

  24. Почему так?

    function tango(x){
        if (false){
            function x(){}
        }
        return x;
    }
    alert(tango(5));
    
    // Opera, Chrome - function x(){}
    // FF - 5
  25. @Domingo, да, это FunctionStatement, поведение варьирует в разных браузерах. Более логичным здесь, конечно, кажется поведение FF. Должно быть специфицировано в ES6 спецификации. Более подробно виды функций описаны в главе 5.

  26. Мне интересно , посредством каких механизмов возможно такое:

    
    void function() {
        (function typed() {
            typed() //too much recursion
        })()
    }()
    
    

    Как возможно , что typed доступна сама из себя , ведь в VO функции typed не попадает же сама функция typed . Но из замыкания тоже не берется этот typed . Поэтому и стало интересно , посредством чего функция доступна сама из себя ?

    На мой взгляд , если функция вызывается сама из себя , то она вызывается через arguments.callee , такое поведение было бы логичным . Или , что было бы ещё логичным , в VO typed попадала бы и сама функция typed но это не так .

  27. Поясните пожалуйста вот этот момент. Тут нету ошибки?

    “– создаётся свойство VO с именем и значением формального параметра; для непереданных параметров – создаётся свойство VO с именем формального параметра и значением undefined;”

  28. @Морозов Дмитрий

    Да, все верно (что Вас смутило?).

    function foo(a, b) {
      
      // При вызове foo(10):
      // VO/AO = {a: 10, b: undefined}
    
      console.log(a, b); // 10, undefined
    }
    
    foo(10);
    
  29. Большое спасибо за серию статей!
    Ни в одной книге по javascript не видел такого подробного объяснения принципов работы.

  30. Добрый день, Дмитрий. Фантастические статьи, открываю JS заново.
    Вопрос по коду.

    alert(x); // function
     
    var x = 10;
    alert(x); // 10
     
    x = 20;
     
    function x() {}
     
    alert(x); // 20
    

    Но почему последний alert покажет 20, если при интерпретации кода перед последним alert x будет ссылаться на функцию?

    Спасибо.

  31. @ivan

    Но почему последний alert покажет 20, если при интерпретации кода перед последним alert x будет ссылаться на функцию?

    Поскольку на стадии “входа в контекст” все переменные и декларации функций уже найдены. Визуально это можно представить, как переменные и функции “подняты” наверх (даже существует термин для этого — hoisting):

    // 1. --- вход в контекст ---
    
    var x;
    function x() {}
    
    // 2. --- исполнение кода контекст ---
    
    alert(x); // function
      
    x = 10;
    alert(x); // 10
      
    x = 20;
        
    alert(x); // 20