Тонкости ECMA-262-3. Часть 5. Функции.

Read this article in: English, Chinese (version 1, version 2).

В этой заметке мы подробней поговорим об одном из основных видов объектов ECMAScript — о функциях. В частности, рассмотрим различные виды функций, определим, как тот или иной вид влияет на объект переменных контекста и, какое содержание имеет цепь областей видимости контекста, связанного с определённым видом функции. Ответим на часто задаваемые вопросы на форумах, вроде: “есть ли отличия (и, если есть, то в чём?), функций созданных следующим образом:

var foo = function () {
  ...
};

от функций, определённых в “привычном” виде”?:

function foo() {
  ...
}

Или, “почему при следующем вызове, функцию обязательно нужно оборачивать в скобки?”:

(function () {
  ...
})();

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

Но, давайте по порядку. Начнём мы с рассмотрения видов функций.

Всего в ECMAScript существует три вида функций, и каждый из них обладает своими особенностями.

Декларация функции (Function Declaration, сокращённо FD) — это функция,

  • обязательно имеющая имя;
  • находящаяся в коде непосредственно: либо на уровне Программа (Program), либо внутри другой функции (FunctionBody);
  • создаваемая на этапе входа в контекст;
  • воздействующая на объект переменных;
  • и объявленная в виде:
function exampleFunc() {
  ...
}

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

Пример (функция вызывается раньше, чем объявлена):

foo();

function foo() {
  alert('foo');
}

Также важным моментом является второй пункт из определения – местоположение декларации функции в коде:

// декларация функции
// находится непосредственно:
// либо в глобальной области
// на уровне Программа
function globalFD() {
  // либо внутри другой функции
  function innerFD() {}
}

Ни в какой другой позиции в коде, декларация функции появиться не может – т.е. нельзя её объявить, например, в зоне выражения, блоке кода и т.д.

Альтернативой (и, даже можно сказать, противоположностью) декларациям функций, являются функции-выражения.

Функция-выражение (Function Expression, сокращённо FE) — это функция,

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

Зоной выражения обозначим любую часть программы, распознаваемую как выражение ECMAScript. Простейшим примером выражения, является выражение присваивания:

var foo = function () {
  ...
};

В данном случае представлена анонимная FE, которая присваивается переменной “foo”. Далее, функция доступна для вызова посредством переменной “foo” – foo().

Так же, как было отмечено в пунктах определения, FE может иметь и опциональное имя:

var foo = function _foo() {
  ...
};

Стоит отметить, что в данном частном случае, снаружи FE доступна всё так же посредством переменной “foo” – foo(), в то время как внутри функции (например, при рекурсивном вызове), возможно использование имени функции “_foo”.

При наличии имени, FE может быть сложно отличима от FD, однако, зная определение, это отличие становится явным и простым: FE всегда находится в зоне выражения. Ниже представлены различные выражения ECMAScript, где связанные с ними функции являются FE:

// в скобках (оператор группировки) может быть только выражение
(function foo() {});

// в инициализаторе массива - всегда выражение
[function bar() {}];

// "запятая" также оперирует выражениями
1, function alsoFE() {};

Также в определении сказано, что FE создаются на этапе исполнения кода контекста и не попадают в объект переменных. Покажем данное поведение на примере:

// FE не доступна ни до объявления
// (т.к. создаётся на этапе выполнения кода контекста),

alert(foo); // "foo" is not defined

(function foo() {});

// ни после, т.к. её вообще нет в VO

alert(foo);  // "foo" is not defined

Спрашивается, зачем они тогда вообще нужны? Ответ очевиден — чтобы использовать их в выражениях и не “загрязнять” объект переменных. Самый простой пример — передача функции в качестве параметра другой функции:

function foo(callback) {
  callback();
}

foo(function bar() {
  alert('foo.bar');
});

foo(function otherFE() {
  alert('foo.otherFE');
});

В том случае, когда FE сохраняется ссылкой в переменную, она остаётся в памяти и доступна посредством этой переменной, поскольку переменные, как мы знаем, воздействуют на VO:

var foo = function () {
  alert('foo');
};

foo();

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

var foo = {};

(function initialize() {

  var x = 10;

  foo.bar = function () {
    alert(x);
  };

})();

foo.bar(); // 10;

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

Видим, что функция foo.bar (благодаря свойству [[Scope]]) имеет доступ к внутренней переменной x функции initialize. И одновременно с этим, x недоступна снаружи. Данная стратегия применяется во многих библиотеках для создания “приватных” данных и сокрытия вспомогательных сущностей. Часто в данной конструкции, имя инициализирующей FE опускается:

(function () {

  // инициализирующее пространство

})();

Ещё примеры FE, создаваемые по условию и не “загрязняющие” VO:

var foo = 10;

var bar = (foo % 2 == 0
  ? function () { alert(0); }
  : function () { alert(1); }
);

bar(); // 0

Итак, ответим на вопрос, упоминавшийся в начале статьи — “зачем при вызове функции сразу же после её создания, нужно оборачивать её в скобки?”. Ответ на этот вопрос вытекает из ограничений на инструкцию-выражение.

Согласно стандарту, инструкция-выражение не может начинаться с открывающей фигурной скобки{, т.к. тогда оно было бы неотличимо от блока, и, также, инструкция-выражение не может начинаться с ключевого слова function, т.к. тогда оно было бы неотличимо от декларации функции. Т.е., если мы опишем вызов функции сразу же после её создания следующим образом (т.е. начиная с ключевого слова function):

function () {
  ...
}();

// или с именем

function foo() {
  ...
}();
 

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

Если мы имеем такое объявление в глобальном коде (т.е. на уровне Программа), парсер должен распознавать такую функцию как декларацию, т.к. она начинается с ключевого слова function. Поэтому в первом случае мы получаем SyntaxError из-за отсутствия имени функции (функция-декларация всегда должна иметь имя).

Во втором случае имя задано (foo), и по идее, декларация функции должна пройти нормально. Однако, мы всё равно имеем ошибку синтаксиса, но уже, касаемо оператора группировки без выражения внутри. Обратите внимание, в данном случае — это именно оператор группировки, который следует за декларацией функции, а не скобки вызова функции! Т.е., если бы мы имели следующий код:

// "foo" - функция-декларация,
// которая создана при входе в контекст

alert(foo); // function

function foo(x) {
  alert(x);
}(1); // а это оператор группировки, но не вызов!

foo(10); // а это уже вызов, 10

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

// декларация функции
function foo(x) {
  alert(x);
}

// оператор группировки
// с выражением
(1);

// еще один оператор группировки
// с другим (функциональным) выражением
(function () {});

// также - внутри выражение
("foo");

// etc

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

if (true) function foo() {alert(1)}

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

Имея все вышесказанное, как мы должны сообщить парсеру, что мы хотим именно запустить функцию сразу же после её объявления? Ответ очевиден. Это должна быть функция-выражение, но не функция-декларация. И самым простым способом создать выражение будет использование упомянутого выше оператора группировки, внутри которого всегда будет находится выражение. Таким образом, парсер распознает код, как функцию-выражение (FE) и неоднозначности не возникнет.

(function foo(x) {
  alert(x);
})(1); // OK, это вызов, не оператор группировки, 1

В примере выше, скобки в конце это уже именно вызов, а не оператор группировки, как было в случае FD.

Обратите внимание, в примере ниже, при вызове функции после её создания, обрамляющие скобки не требуются, т.к. функция уже находится в позиции выражения и парсеру сразу известно, что перед ним FE, которая будет создана в при интерпретации кода контекста:

var foo = {

  bar: function (choose) {
    return choose % 2 != 0 ? 'yes' : 'no';
  }(1)

};

alert(foo.bar); // 'yes'

Как видим, foo.bar является строкой, а не функцией, как может показаться при беглом или невнимательном просмотре кода. Функция здесь используется лишь для инициализации этого свойства в зависимости от параметра — она создаётся и тут же вызывается.

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

Помимо обрамляющих скобок можно использовать любой другой способ преобразования функции к FE, например:

1, function () {
  alert('anonymous function is called');
}();

// или даже так
!function () {
  alert('ECMAScript');
}();

// и любой другой вид
// трансформации FD в FE

...

Однако скобки являются в данном случае наиболее распространенным и элегантным способом.

Кстати сказать, оператор группировки может обрамлять как описание функции без скобок вызова, так и, включая скобки, т.е. оба выражения, описанные ниже, являются правильными FE:

(function () {})();
(function () {}());

Ниже представлен пример, который ни одна из реализаций (на текущий момент) не обрабатывает согласно стандарту:

if (true) {

  function foo() {
    alert(0);
  }

} else {

  function foo() {
    alert(1);
  }

}

foo(); // 1 или 0 ? потестируйте в разных реализациях

Здесь стоит сказать, что, согласно стандарту, данная синтаксическая конструкция вообще ошибочна, поскольку, как мы помним, декларация функции (FD) не может находится в блоке кода (здесь if и else содержат блоки кода). Как было сказано, FD может находится только в двух местах: на уровне Программа и в теле другой функции.

Ошибочна потому, что блок кода может содержать только инструкции (statements). И единственное место, в котором функция может появится в блоке — это одна из таких инструкций — уже рассмотренная выше инструкция-выражение (expression statement). Но она, по определению и, как опять же мы уже отмечали выше, не может начинаться с открывающей фигурной скобки (т.к. неотличимо от блока) и ключевого слова “function” (т.к. неотличимо от FD).

Однако в разделе обработки ошибок стандарт, одним из пунктов, определяет для реализаций возможность расширения синтаксиса программы. И именно одно из таких расширений мы видим в случае объявления функций внутри блоков. Все существующие на сегодня движки не выбрасывают исключение в этом случае, а обрабатывают данную конструкцию. Но, каждый по-своему.

Наличие ветвей if-else подразумевает динамику, выбор, т.е. напрашивается функция-выражение (FE), которая будет создана динамически. Однако большинство реализаций просто создают здесь декларацию функции (FD) ещё на этапе входа в контекст, причём берут последнюю объявленную функцию. Т.е. функция “foo” будет выводить 1, даже несмотря на то, что ветвь else никогда не выполнится.

Однако, всё же, реализация SpiderMonkey (и TraceMonkey) в этой ситуации поступает двояко: с одной стороны она не считает такие функции декларациями (т.е. функция будет создана по условию, в ран-тайме), но и с другой стороны — это не полноценные функции-выражения, т.к. их нельзя вызвать без обрамляющих скобок (снова ошибка парсера – “неотличимо от FD”) и они попадают в объект переменных.

Моё мнение, что SpiderMonkey поступает в данном случае правильно, выделяя негласный средний вид функции — (FE + FD). Данные функции правильно будут созданы в нужное время и исходя из условий, а также, в отличие от FE — будут доступны для вызова снаружи. Данное синтаксическое расширение SpiderMonkey называет инструкцией функции (Function Statement, сокращённо FS); эта терминология упоминается на MDC. Создатель JavaScript Brendan Eich также отмечал данный вид функций, присутствующий в реализации SpiderMonkey.

В том случае, когда FE имеет имя (named function expression, сокращённо NFE), в силу вступает одна важная особенность. Как нам известно из определения, (и, как мы видели из вышеописанных примеров), функции-выражения не воздействуют на объект переменных контекста (что означает невозможность обратиться к ним по имени ни до, ни после объявления). Однако, FE имеет возможность обращаться к себе по имени при рекурсивном вызове:

(function foo(bar) {
  
  if (bar) {
    return;
  }
  
  foo(true); // имя "foo" доступно

})();

// а снаружи, правильно, недоступно

foo(); // "foo" is not defined

Где же хранится имя “foo”? В объекте переменных самой foo? Нет, т.к. никто не определял никакого имени “foo” внутри foo. В родительском объекте переменных контекста, породившего foo? Тоже нет, т.к. это следует из определения — FE не воздействуют на VO — что мы и видим при вызове за пределами foo. Где же тогда?

А дело вот в чём. Когда интерпретатор, на стадии исполнения кода контекста, встречает именованную FE, он, перед созданием самой FE, создаёт вспомогательный спецобъект и добавляет его спереди к текущей цепи областей видимости. Далее создаётся сама FE, и в этот момент (как нам известно из статьи о Scope chain) в неё свойством записывается [[Scope]] — цепь областей видимости контекста, породившего функцию (т.е. в [[Scope]] присутствует этот спецобъект). Далее, в спецобъект добавляется одно единственное свойство — имя FE; значением свойства является ссылка на эту FE. И последним действием, спецобъект удаляется из Scope chain порождающего функцию контекста. Отобразить псевдокодом можно так:

__specialObject = {};

Scope = __specialObject + Scope;

foo = new FunctionExpression;
foo.[[Scope]] = Scope;
__specialObject.foo = foo; // {DontDelete}, {ReadOnly}

delete Scope[0]; // удалить __specialObject из начала Scope

Таким образом, за пределами функции данное имя недоступно (т.к. его нет в Scope), но спецобъект успел записаться в [[Scope]] функции, и там это имя определено.

Стоит однако отметить, что не всё так гладко в этом моменте с реализациями. Некоторые из них, например, Rhino, записывают это опциональное имя не в спецобъект, а в сам объект активации FE. Реализация же от Microsoft — JScript, полностью нарушая правила FE, вообще сохраняет это имя во внешнем объекте переменных, и оно становится доступно снаружи.

Касательно реализаций, всё в той же SpiderMonkey, есть одна особенность, связанная с этим спецобъектом, которую, в принципе, можно считать багом (хотя реализовано всё согласно стандарту, так что это — больше недочёт спецификации). Связана она с механизмом разрешения имён идентификаторов: последовательный опрос Scope chain является двумерным и при разрешении имени идентификатора, учитывает также и прототипы объектов цепи.

Наблюдать данный механизм в действии можно, если определить свойство в Object.prototype и обратиться к “несуществующей” переменной из функции, определённой в глобальном контексте. Таким образом, при разрешении имени “x” в примере ниже, мы дойдём до глобального объекта, но и там имя “x” не будет найдено. Однако в SpiderMonkey глобальный объект наследует от Object.prototype и, соответственно, имя “x” будет найдено в нём:

Object.prototype.x = 10;

(function () {
  alert(x); // 10
})();

Объекты активации прототипов не имеют. При тех же начальных условиях, данное поведение можно видеть на примере вложенной функции. Если определить локальную переменную “x” и объявить вложенную функцию (FD или анонимную FE), то при обращении из вложенной функции к внешней переменной “x”, значение её будет найдено из функции выше (т.е. там, где она и находится), а не из Object.prototype:

Object.prototype.x = 10;

function foo() {

  var x = 20;

  // декларация функции  

  function bar() {
    alert(x); 
  }

  bar(); // 20, из AO(foo)

  // аналогично при анонимной FE
  
  (function () {
    alert(x); // 20, также из AO(foo)
  })();
 
}

foo();

Некоторых реализации, являясь исключением, всё же задают прототип объектам активации. Так, например, в реализации Blackberry значение “x” из примера выше будет определено как 10. Т.е. до объекта активации foo мы не дойдём, т.к. значение будет найдено в Object.prototype:

AO(bar или анонимной функции) -> нет ->
AO(bar или анонимной функции).[[Prototype]] -> да - 10

И абсолютно аналогичную ситуацию можно наблюдать в SpiderMonkey со спецобъектом именованной FE. Данный спецобъект является (по стандарту) обычным объектом — “как если бы new Object()”, и соответственно, должен наследоваться от Object.prototype, что мы и видим в реализации SpiderMonkey. Остальные реализации (включая новый TraceMonkey) не задают прототипа спецобъекту:

function foo() {

  var x = 10;

  (function bar() {

    alert(x); // 20, а не 10, т.к. до AO(foo) не доходит

    // "x" найдено по цепи:
    // AO(bar) - нет -> __specialObject(bar) -> нет
    // __specialObject(bar).[[Prototype]] - да: 20

  })();
}

Object.prototype.x = 20;

foo();

Реализация ECMAScript от Microsoft – JScript, встроенная в Internet Explorer, на текущий момент (вплоть до версии JScript 5.8 – IE8) имеет ряд багов, связанных с именованными функциями-выражениями (NFE). Каждый из этих багов полностью расходится со стандартом ECMA-262-3; некоторые из них чреваты серьёзными ошибками.

Во-первых, JScript в этом моменте нарушает главную особенность FE – то, что они не должны попадать в объект переменных по имени функции. Опциональное имя FE, которое должно записываться в спецобъект, доступный лишь при активации функции (и нигде более), здесь записывается прямо во внешний объект переменных. Более того, именованная FE трактуется в JScript, как декларация функции (FD), т.е. создаётся при входе в контекст и доступна до объявления:

// FE доступна в объекте переменных
// по опциональному имени и
// до объявления, - как FD
testNFE();

(function testNFE() {
  alert('testNFE');
});

// а также - после
// объявления, как FD; опциональное
// имя осталось висеть в объекте переменных
testNFE();

В общем, полное нарушение правил.

Во-вторых, в случае сохранения ссылки на именованную FE в переменную сразу при описании переменной и операции присваивания, JScript создаёт два разных объекта-функции. Данное поведение сложно назвать логичным (особенно, учитывая то, что за пределами самой NFE, её имя вообще не должно быть доступно):

var foo = function bar() {
  alert('foo');
};

alert(typeof bar); // "function", NFE вновь осталась в VO - уже ошибка

// но, дальше - больше
alert(foo === bar); // false!

foo.x = 10;
alert(bar.x); // undefined

// однако, два объекта выполняют
// одинаковые действия

foo(); // "foo"
bar(); // "foo"

Вновь – полный беспорядок.

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

(function bar() {});

var foo = bar;

alert(foo === bar); // true

foo.x = 10;
alert(bar.x); // 10

Этот момент объясним. На самом деле, создаётся так же два объекта, но в дальнейшем остаётся, действительно, только один. Если опять учесть, что NFE здесь трактуется, как декларация функции (FD), то ещё на этапе входа в контекст создастся FD bar. Далее уже на этапе интерпретации кода создаётся второй объект — функция-выражение (FE) bar, которая нигде не сохраняется. Соответственно, поскольку ссылок на bar нет, она тут же удаляется. Таким образом остаётся только один объект — FD bar, ссылка на который и присваивается переменной foo.

В-третьих, касаемо косвенной ссылки функции на саму себя — посредством arguments.callee, опять же, в случае описания NFE с сохранением ссылки на неё в переменную по месту (т.е. использованием двух объектов), arguments.callee будет указывать на тот объект, чьим именем была активирована функция (а точнее — функции, т.к. их две):

var foo = function bar() {

  alert([
  	arguments.callee === foo,
  	arguments.callee === bar
  ]);

};

foo(); // [true, false]
bar(); // [false, true]

В-четвёртых, поскольку JScript трактует NFE как обычную FD, то на неё не действуют правила условных операторов (т.е. как и FD, NFE будет создана ещё при входе в контекст, причём будет взято последнее описание в коде):

var foo = function bar() {
  alert(1);
};

if (false) {

  foo = function bar() {
    alert(2);
  };

}
bar(); // 2
foo(); // 1

Данное поведение также можно “логично” разобрать. При входе в контекст была создана последняя встретившаяся “FD” для имени bar, т.е. функция с alert(2);. Далее, при интерпретации кода, создаётся уже новая функция — FE bar, ссылка на которую и присваивается переменной foo. Таким образом (поскольку дальше по коду if-блок с условием false — недостижим), активация foo выводит alert(1);. Логика ясна, но с учётом багов IE, я взял слово “логично” в кавычки, т.к. подобная реализация явно сбитая и завязана на баги JScript.

Ну и пятый баг NFE в JScript связан с инициализацией свойств глобального объекта посредством обычного присвоения имени значения. Т.к. NFE трактуется здесь как FD и, соответственно, попадает в объект переменных, присвоение ссылки свойству без var (т.е. не переменной, а обычному свойству глобального объекта), при условии, что имя самой функции совпадает с именем свойства, данное свойство не становится глобальным.

(function () {

  // без var должна быть не переменная в локальном
  // контексте, а свойство глобального объекта

  foo = function foo() {};

})();

// однако, за пределами контекста
// анонимной функции, имя foo
// не существует

alert(typeof foo); // undefined

Опять же, “логика” понятна: декларация функции foo попадает в объект активации локального контекста анонимной функции ещё при входе в контекст. И к моменту интерпретации кода контекста, имя foo уже содержится в AO, т.е. считается локальным. Соответственно, при операции присваивания просто обновляется, уже существующее в AO свойство “foo”, но не создаётся новое свойство глобального объекта, как должно быть по логике вещей ECMA-262-3.

Данный тип объектов-функций обособлен от FD и FE, т.к. также имеет свои особенности. Основная особенность – это то, что данные функции в качестве [[Scope]] имеют лишь глобальный объект:

var x = 10;

function foo() {
  
  var x = 20;
  var y = 30;
  
  var bar = new Function('alert(x); alert(y);');
  
  bar(); // 10, "y" is not defined

}

Т.е. видим, что в [[Scope]] функции “bar” отсутствует AO контекста функции “foo” (не доступна переменная “y”, а переменная “x” берётся из глобального контекста). Кстати, обратите внимание, конструктор Function можно вызывать и с new и без, в данном случае эти записи будут эквивалентны.

Следующая особенность данных функций связана с тождественными правилами грамматики (Equated Grammar Productions) и объединёнными объектами (Joined Objects). Данный механизм введён спецификацией, как предложение по оптимизации (однако, реализации вправе не осуществлять данную оптимизацию). К примеру, если у нас есть массив из ста элементов, который заполняется в цикле функциями, реализация вправе задействовать механизм объединённых объектов. В итоге, может быть создана лишь одна функция для всех элементов массива:

var a = [];

for (var k = 0; k < 100; k++) {
  a[k] = function () {}; // возможно, объединённые объекты
}

Но, функции, порождённые конструктором Function, никогда не объединяются:

var a = [];

for (var k = 0; k < 100; k++) {
  a[k] = Function(''); // всегда 100 разных функций
}

Ещё пример, касающийся объединения:

function foo() {

  function bar(z) {
    return z * z;
  }

  return bar;
}

var x = foo();
var y = foo();

Здесь также, реализация вправе объединить объекты x и y (и использовать один объект), поскольку функции физически (включая их внутреннее свойство [[Scope]]) не различимы. Поэтому, функции, порождённые с помощью конструктора Function всегда более затратны по ресурсам.

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

F = new NativeObject();

// свойство [[Class]] всегда "Function"
F.[[Class]] = "Function"

// прототип функции
F.[[Prototype]] = Function.prototype

// объект вызова, именно он активируется
// и создаёт контекст посредством выражения вызова F()
F.[[Call]] = callObject

// встроенный конструктор всех
// объектов функций, далее, этот
// встроенный конструктор вызывает F.[[Call]],
// инициализируя созданный объект
F.[[Construct]] = internalConstructor

// цепь областей видимости
// порождающего контекста
F.[[Scope]] = activeContext.Scope
// если функция была создана
// через new Function(...), то
F.[[Scope]] = globalContext.Scope

// количество описанных формальных параметров
F.length = countParameters

// прототип порождаемых от F объектов
__objectPrototype = new Object();
__objectPrototype.constructor = F // {DontEnum}, не выводится в цикле
F.prototype = __objectPrototype

return F

Обратите внимание, F.[[Prototype]] – это прототип функции (конструктора), а F.prototype – это прототип порождаемых от функции объектов (просто часто бывает путаница в терминологии, и F.prototype называют “прототипом конструктора”, что неверно).

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

Спецификация ECMAScript:

Другие статьи:


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