深刻理解JavaScript内部原理: function(转)

本文是翻译http://dmitrysoshnikov.com/ecmascript/chapter-5-functions/#introduction前端

概要
In this article we will talk about one of the general ECMAScript objects — about functions. In particular, we will go through various types of functions, will define how each type influencesvariables object of a context and what is contained in the scope chain of each function. We will answer the frequently asked questions such as: “is there any difference (and if there are, what are they?) between functions created as follows:算法

在这一章节中,咱们来探讨下ECMAScript中一个很重要的对象-函数。咱们将详细讲解一下各类类型的函数是如何影响上下文的变量对象以及每一个函数的做用域链都包含什么,咱们将回答诸如像下面这样的问题:下面声明的函数有什么区别么?(若是有,区别是什么)。express

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

from functions defined in a “habitual” way?”:数组

传统的函数声明是:闭包

function foo() {
  ...
}

Or, “why in the next call, the function has to be surrounded with parentheses?”:ecmascript

或者,下面的函数调用,为何要用括号包围起来。ide

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

Since these articles relay on earlier chapters, for full understanding of this part it is desirable to read Chatper 2. Variable object and Chapter 4. Scope chain, since we will actively use terminology from these chapters.函数

But let us give one after another. We begin with consideration of function types.oop

函数类型
In ECMAScript there are three function types and each of them has its own features.优化

在ECMAScript中,有三种不一样的函数类型,而且他们都有本身的特色。

函数声明
A Function Declaration (abbreviated form is FD) is a function which:
函数声明(简写FD)是这样的一个函数
has an obligatory name;
in the source code position it is positioned: either at the Program level or directly in the body of another function (FunctionBody);
is created on entering the context stage;
influences variable object;
and is declared in the following way:
有一个特定的名称
在源码中的位置:要么处于程序级(Program level),要么处于其它函数的主体(FunctionBody)中
在进入上下文阶段建立
影响变量对象
如下面的方式声明

function exampleFunc() {
  ...
}

The main feature of this type of functions is that only they influence variable object (they are stored in the VO of the context). This feature defines the second important point (which is a consequence of a variable object nature) — at the code execution stage they are already available (since FD are stored in the VO on entering the context stage — before the execution begins).

这种类型的函数最重要的特色就是它影响变量对象(存储在变量对象的上下文中),这个特性也说明了第二个很重要的观点(它是变量对象特性的结果)在代码执行阶段它们已经可用(由于FD在进入上下文阶段已经存在于VO中——代码执行以前)。

Example (function is called before its declaration in the source code position):

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

What’s also important is the position at which the funcion is defined in the source code (see the second bullet in the Function declaration definition above):

另一个重点知识点是上述定义中的第二点——函数声明在源码中的位置:

// function can be declared:
// 1) directly in the global context
function globalFD() {
  // 2) or inside the body
  // of another function
  function innerFD() {}
}

These are the only two positions in code where a function may be declared (i.e. it is impossible to declare it in an expression position or inside a code block).

There’s one alternative to function declarations which is called function expressions, which we are about to cover.

只有这2个位置能够声明函数,也就是说:不可能在表达式位置或一个代码块中定义它。

另一种能够取代函数声明的方式是函数表达式,解释以下:

函数表达式
A Function Expression (abbreviated form is FE) is a function which:
函数表达式(简写FE)是这样的一个函数
in the source code can only be defined at the expression position;
can have an optional name;
it’s definition has no effect on variable object;
and is created at the code execution stage.
在源码中须出如今表达式的位置
有可选的名称
不会影响变量对象
在代码执行阶段建立
The main feature of this type of functions is that in the source code they are always in theexpression position. Here’s a simple example such assignment expression:

这种函数类型的主要特色在于它在源码中老是处在表达式的位置。最简单的一个例子就是一个赋值声明:

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

This example shows how an anonymous FE is assigned to foo variable. After that the function is available via foo name — foo().

The definition states that this type of functions can have an optional name:

该例演示是让一个匿名函数表达式赋值给变量foo,而后该函数能够用foo这个名称进行访问——foo()。

同时和定义里描述的同样,函数表达式也能够拥有可选的名称:

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

What’s important here to note is that from the outside FE is accessible via variable foo — foo(), while from inside the function (for example, in the recursive call), it is also possible to use _fooname.

When a FE is assigned a name it can be difficult to distinguish it from a FD. However, if you know the definition, it is easy to tell them apart: FE is always in the expression position. In the following example we can see various ECMAScript expressions in which all the functions are FE:

须要注意的是,在外部FE经过变量“foo”来访问——foo(),而在函数内部(如递归调用),有可能使用名称“_foo”。

若是FE有一个名称,就很难与FD区分。可是,若是你明白定义,区分起来就简单明了:FE老是处在表达式的位置。在下面的例子中咱们能够看到各类ECMAScript 表达式:

// in parentheses (grouping operator) can be only an expression
(function foo() {});
 
// in the array initialiser – also only expressions
[function bar() {}];

// comma also operates with expressions
1, function baz() {};

表达式定义里说明:FE只能在代码执行阶段建立并且不存在于变量对象中,让咱们来看一个示例行为:

// FE is not available neither before the definition
// (because it is created at code execution phase),
 
alert(foo); // "foo" is not defined
 
(function foo() {});
 
// nor after, because it is not in the VO
 
alert(foo);  // "foo" is not defined

至关一部分问题出现了,咱们为何须要函数表达式?答案是很显然的——在表达式中使用它们,”不会污染”变量对象。最简单的例子是将一个函数做为参数传递给其它函数。

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

在上述例子里,FE赋值给了一个变量(也就是参数),函数将该表达式保存在内存中,并经过变量名来访问(由于变量影响变量对象),以下:

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]]属性)访问到函数initialize的内部变量“x”。同时,“x”在外部不能直接访问。在许多库中,这种策略经常使用来建立”私有”数据和隐藏辅助实体。在这种模式中,初始化的FE的名称一般被忽略:

(function () {
 
  // initializing scope
 
})();

还有一个例子是:在代码执行阶段经过条件语句进行建立FE,不会污染变量对象VO。

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

关于圆括号的问题

让咱们回头并回答在文章开头提到的问题——”为什么在函数建立后的当即调用中必须用圆括号来包围它?”,答案就是:表达式句子的限制就是这样的。

根据标准,表达式语句不能以一个大括号{开始是由于他很难与代码块区分,一样,他也不能以函数关键字开始,由于很难与函数声明进行区分。即,因此,若是咱们定义一个当即执行的函数,在其建立后当即按如下方式调用:

function () {
  ...
}();
 
// or even with a name
 
function foo() {
  ...
}();

咱们使用了函数声明,上述2个定义,解释器在解释的时候都会报错,可是可能有多种缘由。

若是在全局代码里定义(也就是程序级别),解释器会将它看作是函数声明,由于他是以function关键字开头,第一个例子,咱们会获得SyntaxError错误,是由于函数声明没有名字(咱们前面提到了函数声明必须有名字)。

第二个例子,咱们有一个名称为foo的一个函数声明正常建立,可是咱们依然获得了一个语法错误——没有任何表达式的分组操做符错误。在函数声明后面他确实是一个分组操做符,而不是一个函数调用所使用的圆括号。因此若是咱们声明以下代码:

// "foo" is a function declaration
// and is created on entering the context
 
alert(foo); // function
 
function foo(x) {
  alert(x);
}(1); // and this is just a grouping operator, not a call!
 
foo(10); // and this is already a call, 10

上述代码是没有问题的,由于声明的时候产生了2个对象:一个函数声明,一个带有1的分组操做,上面的例子能够理解为以下代码:

// function declaration
function foo(x) {
  alert(x);
}
 
// a grouping operator
// with the expression
(1);
 
// another grouping operator with
// another (function) expression
(function () {});
 
// also - the expression inside
("foo");

根据规范,上述代码是错误的(一个表达式语句不能以function关键字开头),但下面的例子就没有报错,想一想为何?

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

The construction above by the specification is syntactically incorrect (an expression statement cannot begin with a function keyword), but as we will see below, none of the implementations provide the syntax error, but handle this case, though, every in it’s own manner.

咱们若是来告诉解释器:我就像在函数声明以后当即调用,答案是很明确的,你得声明函数表达式function expression,而不是函数声明function declaration,而且建立表达式最简单的方式就是用分组操做符括号,里边放入的永远是表达式,因此解释器在解释的时候就不会出现歧义。在代码执行阶段这个的function就会被建立,而且当即执行,而后自动销毁(若是没有引用的话)。

(function foo(x) {
  alert(x);
})(1); // OK, it's a call, not a grouping operator, 1

上述代码就是咱们所说的在用括号括住一个表达式,而后经过(1)去调用。

注意,下面一个当即执行的函数,周围的括号不是必须的,由于函数已经处在表达式的位置,解析器知道它处理的是在函数执行阶段应该被建立的FE,这样在函数建立后当即调用了函数。

var foo = {
 
  bar: function (x) {
    return x % 2 != 0 ? 'yes' : 'no';
  }(1)
 
};
 
alert(foo.bar); // 'yes'

就像咱们看到的,foo.bar是一个字符串而不是一个函数,这里的函数仅仅用来根据条件参数初始化这个属性——它建立后并当即调用。

所以,”关于圆括号”问题完整的答案以下:当函数不在表达式的位置的时候,分组操做符圆括号是必须的——也就是手工将函数转化成FE。若是解析器知道它处理的是FE,就不必用圆括号
Apart from surrounding parentheses it is possible to use any other way of transformation of a function to FE type. For example:

除了大括号之外,以下形式也能够将函数转化为FE类型,例如:

1,

function () {
  alert('anonymous function is called');
}();
 
// or this one
!function () {
  alert('ECMAScript');
}();
 
// and any other manual
// transformation

...

可是,在这个例子中,圆括号是最简洁的方式。

顺便提一句,组表达式包围函数描述能够没有调用圆括号,也可包含调用圆括号,即,下面的两个表达式都是正确的FE。

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

实现扩展: 函数语句

下面的代码,根据贵方任何一个function声明都不该该被执行:

if (true) {
 
  function foo() {
    alert(0);
  }
 
} else {
 
  function foo() {
    alert(1);
  }
 
}
 
foo(); // 1 or 0 ? test in different implementations

这里有必要说明的是,按照标准,这种句法结构一般是不正确的,由于咱们还记得,一个函数声明(FD)不能出如今代码块中(这里if和else包含代码块)。咱们曾经讲过,FD仅出如今两个位置:程序级(Program level)或直接位于其它函数体中。

由于代码块仅包含语句,因此这是不正确的。能够出如今块中的函数的惟一位置是这些语句中的一个——上面已经讨论过的表达式语句。可是,按照定义它不能以大括号开始(既然它有别于代码块)或以一个函数关键字开始(既然它有别于FD)。

可是,在标准的错误处理章节中,它容许程序语法的扩展执行。这样的扩展之一就是咱们见到的出如今代码块中的函数。在这个例子中,现今的全部存在的执行都不会抛出异常,都会处理它。可是它们都有本身的方式。

if-else分支语句的出现意味着一个动态的选择。即,从逻辑上来讲,它应该是在代码执行阶段动态建立的函数表达式(FE)。可是,大多数执行在进入上下文阶段时简单的建立函数声明(FD),并使用最后声明的函数。即,函数foo将显示”1″,事实上else分支将永远不会执行。

可是,SpiderMonkey (和TraceMonkey)以两种方式对待这种状况:一方面它不会将函数做为声明处理(即,函数在代码执行阶段根据条件建立),但另外一方面,既然没有括号包围(再次出现解析错误——”与FD有别”),他们不能被调用,因此也不是真正的函数表达式,它储存在变量对象中。

我我的认为这个例子中SpiderMonkey 的行为是正确的,拆分了它自身的函数中间类型——(FE+FD)。这些函数在合适的时间建立,根据条件,也不像FE,倒像一个能够从外部调用的FD,SpiderMonkey将这种语法扩展 称之为函数语句(缩写为FS);该语法在MDC中说起过。

命名函数表达式的特性

当函数表达式FE有一个名称(称为命名函数表达式,缩写为NFE)时,将会出现一个重要的特色。从定义(正如咱们从上面示例中看到的那样)中咱们知道函数表达式不会影响一个上下文的变量对象(那样意味着既不可能经过名称在函数声明以前调用它,也不可能在声明以后调用它)。可是,FE在递归调用中能够经过名称调用自身。

(function foo(bar) {
 
  if (bar) {
    return;
  }
 
  foo(true); // "foo" name is available
 
})();
 
// but from the outside, correctly, is not
 
foo(); // "foo" is not defined

foo”储存在什么地方?在foo的活动对象中?不是,由于在foo中没有定义任何”foo”。在上下文的父变量对象中建立foo?也不是,由于按照定义——FE不会影响VO(变量对象)——从外部调用foo咱们能够实实在在的看到。那么在哪里呢?

如下是关键点。当解释器在代码执行阶段遇到命名的FE时,在FE建立以前,它建立了辅助的特定对象,并添加到当前做用域链的最前端。而后它建立了FE,此时(正如咱们在第四章 做用域链知道的那样)函数获取了[[Scope]] 属性——建立这个函数上下文的做用域链)。此后,FE的名称添加到特定对象上做为惟一的属性;这个属性的值是引用到FE上。最后一步是从父做用域链中移除那个特定的对象。让咱们在伪码中看看这个算法:

specialObject = {};
 
Scope = specialObject + Scope;
 
foo = new FunctionExpression;
foo.[[Scope]] = Scope;
specialObject.foo = foo; // {DontDelete}, {ReadOnly}
 
delete Scope[0]; // remove specialObject from the front of scope chain

所以,在函数外部这个名称不可用的(由于它不在父做用域链中),可是,特定对象已经存储在函数的[[scope]]中,在那里名称是可用的。

可是须要注意的是一些实现(如Rhino)不是在特定对象中而是在FE的激活对象中存储这个可选的名称。Microsoft 中的执行彻底打破了FE规则,它在父变量对象中保持了这个名称,这样函数在外部变得能够访问。

NFE和SpiderMonkey
Let’s have a look at how different implementations handle this problem. Some versions of SpiderMonkey have one feature related to special object which can be treated as a bug (although all was implemented according to the standard, so it is more of an editorial defect of the specification). It is related to the mechanism of the identifier resolution: the scope chain analysis istwo-dimensional and when resolving an identifier it considers the prototype chain of every object in the scope chain as well.

说到实现,部分版本的SpiderMonkey有一个与上述提到的特殊对象相关的特性,这个特性也能够看做是个bug(既然全部的实现都是严格遵循标准的,那么这个就是标准的问题了)。 此特性和标识符处理相关: 做用域链的分析是二维的,在标识符查询的时候,还要考虑做用域链中每一个对象的原型链。

We can see this mechanism in action if we define a property in Object.prototype and use a “nonexistent” variable from the code. In the following example when resolving the name x the global object is reached without finding x. However since in SpiderMonkey the global object inherits from Object.prototype the name x is resolved there:

当在Object.prototype对象上定义一个属性,并将该属性值指向一个“根本不存在”的变量时,就可以体现该特性。 好比,以下例子中的变量“x”,在查询过程当中,经过做用域链,一直到全局对象也是找不到“x”的。 然而,在SpiderMonkey中,全局对象继承自Object.prototype,因而,对应的值就在该对象中找到了:

Object.prototype.x = 10;
 
(function () {
  alert(x); // 10
})();

Activation objects do not have prototypes. With the same start conditions, it is possible to see the same behavior in the example with inner function. If we were to define a local variable x and declare inner function (FD or anonymous FE) and then to reference x from the inner function, this variable would be resolved normally in the parent function context (i.e. there, where it should be and is), instead of in Object.prototype:

活跃对象是没有原型一说的。能够经过内部函数还证实。 若是在定义一个局部变量“x”并声明一个内部函数(FD或者匿名的FE),而后,在内部函数中引用变量“x”,这个时候该变量会在上层函数上下文中查询到(理应如此),而不是在Object.prototype中:

Object.prototype.x = 10;
 
function foo() {
 
  var x = 20;
 
  // function declaration  
 
  function bar() {
    alert(x);
  }
 
  bar(); // 20, from AO(foo)
 
  // the same with anonymous FE
 
  (function () {
    alert(x); // 20, also from AO(foo)
  })();
 
}
 
foo();

Some implementations set a prototype for activation objects, which is an exception compared to most of other implementations. So, in the Blackberry implementation value x from the above example is resolved to 10. I.e. do not reach activation object of foo since value is found in Object.prototype:

在有些实现中,存在这样的异常:它们会在活跃对象设置原型。比方说,在Blackberry的实现中,上述例子中变量“x”值就会变成10。 由于,“x”从Object.prototype中就找到了:

AO(bar FD or anonymous FE) -> no ->
AO(bar FD or anonymous FE).[[Prototype]] -> yes - 10

当出现有名字的FE的特殊对象的时候,在SpiderMonkey中也是有一样的异常。该特殊对象是常见对象 —— “和经过new Object()表达式产生的同样”。 相应地,它也应当继承自Object.prototype,上述描述只针对SpiderMonkey(1.7版本)。其余的实现(包括新的TraceMonkey)是不会给这个特殊对象设置原型的:

function foo() {
 
  var x = 10;
 
  (function bar() {
 
    alert(x); // 20, but not 10, as don't reach AO(foo) 
 
    // "x" is resolved by the chain:
    // AO(bar) - no -> __specialObject(bar) -> no
    // __specialObject(bar).[[Prototype]] - yes: 20
 
  })();
}
 
Object.prototype.x = 20;
 
foo();

NFE and JScript
ECMAScript implementation from Microsoft — JScript which is currently built into Internet Explorer (up to JScript 5.8 — IE8) has a number of bugs related with named function expressions (NFE). Every of these bugs completely contradicts ECMA-262-3 standard; some of them may cause serious errors.

First, JScript in this case breaks the main rule of FE that they should not be stored in the variable object by name of functions. An optional FE name which should be stored in the special object and be accessible only inside the function itself (and nowhere else) here is stored directly in the parent variable object. Moreover, named FE is treated in JScript as the function declaration (FD), i.e. is created on entering the context stage and is available before the definition in the source code:

微软的实现——JScript,是IE的JS引擎(截至本文撰写时最新是JScript5.8——IE8),该引擎与NFE相关的bug有不少。每一个bug基本上都和ECMA-262-3rd标准是彻底违背的。 有些甚至会引起严重的错误。

第一,针对上述这样的状况,JScript彻底破坏了FE的规则:不该当将函数名字保存在变量对象中的。 另外,FE的名字应当保存在特殊对象中,而且只有在函数自身内部才能够访问(其余地方均不能够)。而JScript却将其直接保存在上层上下文的变量对象中。 而且,JScript竟然还将FE以FD的方式处理,在进入上下文的时候就将其建立出来,并在定义以前就能够访问到:

// FE is available in the variable object
// via optional name before the
// definition like a FD
testNFE();
 
(function testNFE() {
  alert('testNFE');
});
 
// and also after the definition
// like FD; optional name is
// in the variable object
testNFE();

正如你们所见,彻底破坏了FE的规则。

第二,在声明同时,将NFE赋值给一个变量的时候,JScript会建立两个不一样的函数对象。 这种行为感受彻底不符合逻辑(特别是考虑到在NFE外层,其名字根本是没法访问到的):

var foo = function bar() {
  alert('foo');
};
 
alert(typeof bar); // "function", NFE again in the VO – already mistake
 
// but, further is more interesting
alert(foo === bar); // false!
 
foo.x = 10;
alert(bar.x); // undefined
 
// but both function make
// the same action
 
foo(); // "foo"
bar(); // "foo"

然而,要注意的是: 当将NFE和赋值给变量这两件事情分开的话(好比,经过组操做符),在定义好后,再进行变量赋值,这样,两个对象就相同了,返回true:

(function bar() {});
 
var foo = bar;
 
alert(foo === bar); // true
 
foo.x = 10;
alert(bar.x); // 10

这个时候就好解释了。实施上,一开始的确建立了两个对象,不过以后就只剩下一个了。这里将NFE以FD的方式来处理,而后,当进入上下文的时候,FD bar就建立出来了。 在这以后,到了执行代码阶段,又建立出了第二个对象 —— FE bar,该对象不会进行保存。相应的,因为没有变量对其进行引用,随后FE bar对象就被移除了。 所以,这里就只剩下一个对象——FD bar对象,对该对象的引用就赋值给了foo变量。

第三,经过arguments.callee对一个函数进行间接引用,它引用的是和激活函数名一致的对象(事实上是——函数,由于有两个对象):

var foo = function bar() {
 
  alert([
    arguments.callee === foo,
    arguments.callee === bar
  ]);
 
};
 
foo(); // [true, false]
bar(); // [false, true]

Fourthly, as JScript treats NFE as usual FD, it is not submitted to conditional operators rules, i.e. just like a FD, NFE is created on entering the context and the last definition in a code is used:

第四,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函数的调用会打印出1。 尽管“逻辑上”是对的,可是这个仍然算是IE的bug。由于它明显就破坏了实现的规则,因此我这里用了引号“逻辑上”。

第五个JScript中NFE的bug和经过给一个未受限的标识符赋值(也就是说,没有var关键字)来建立全局对象的属性相关。 因为这里NFE会以FD的方式来处理,并相应地会保存在变量对象上,赋值给未受限的标识符(不是给变量而是给全局对象的通常属性), 当函数名和标识符名字相同的时候,该属性就不会是全局的了。

(function () {
     
      // without var not a variable in the local
      // context, but a property of global object
     
      foo = function foo() {};
     
    })();

 
// however from the outside of
// anonymous function, name foo
// is not available
 
alert(typeof foo); // undefined

Again, the “logic” is clear: the function declaration foo gets to the activation object of a local context of anonymous function on entering the context stage. And at the moment of code execution stage, the name foo already exists in AO, i.e. is treated as local. Accordingly, at assignment operation there is simply an update of already existing in AO property foo, but not creation of new property of global object as should be according to the logic of ECMA-262-3.

这里从“逻辑上”又是能够解释通的: 进入上下文时,函数声明在匿名函数本地上下文的活跃对象中。 当进入执行代码阶段的时候,由于foo这个名字已经在AO中存在了(本地),相应地,赋值操做也只是简单的对AO中的foo进行更新而已。 并无在全局对象上建立新的属性。

经过Function构造器建立的函数
This type of function objects is discussed separately from FD and FE since it also has its own features. The main feature is that the [[Scope]] property of such functions contains only global object:

这类函数有别于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
 
}

We see that the [[Scope]] of bar function does not contain AO of foo context — the variable “y” is not accessible and the variable “x” is taken from the global context. By the way, pay attention, theFunction constructor can be used both with new keyword and without it, in this case these variants are equivalent.

咱们看到bar函数的[[Scope]]属性并未包含foo上下文的AO —— 变量“y”是没法访问的,而且变量“x”是来自全局上下文。 顺便提下,这里要注意的是,Function构造器能够经过new关键字和省略new关键字两种用法。上述例子中,这两种用法都是同样的。

The other feature of such functions is related with Equated Grammar Productions and Joined Objects. This mechanism is provided by the specification as suggestion for the optimization (however, implementations have the right not to use such optimization). For example, if we have an array of 100 elements which is filled in a loop with functions, then implementation can use this mechanism of joined objects. As a result only one function object for all elements of an array can be used:

此类函数其余特性则和同类语法产生式以及联合对象有关。 该机制在标准中建议在做优化的时候采用(固然,具体的实现者也彻底有权利不使用这类优化)。比方说,有100元素的数组,在循环数组过程当中会给数组每一个元素赋值(函数), 这个时候,实现的时候就能够采用联合对象的机制了。这样,最终全部的数组元素都会引用同一个函数(只有一个函数):

var a = [];
 
for (var k = 0; k < 100; k++) {
  a[k] = function () {}; // possibly, joined objects are used
}

可是,经过Function构造器建立的函数就没法使用联合对象了:

var a = [];
 
for (var k = 0; k < 100; k++) {
  a[k] = Function(''); // always 100 different funcitons
}

下面是另一个和联合对象相关的例子:

function foo() {
 
  function bar(z) {
    return z * z;
  }
 
  return bar;
}
 
var x = foo();
var y = foo();

Here also implementation has the right to join objects x and y (and to use one object) because functions physically (including their internal [[Scope]] property) are not distinguishable. Therefore, the functions created via Function constructor always require more memory resources.

上述例子,在实现过程当中一样可使用联合对象。来使得x和y引用同一个对象,由于函数(包括它们内部的[[Scope]]属性)物理上是不可分辨的。 所以,经过Function构造器建立的函数老是会占用更多内存资源。

函数建立的算法
The pseudo-code of function creation algorithm (except steps with joined objects) is described below. This description helps to understand in more detail which function objects exist in ECMAScript. The algorithm is identical for all function types.

以下所示使用伪代码表示的函数建立的算法(不包含联合对象的步骤)。有助于理解ECMAScript中的函数对象。此算法对全部函数类型都是同样的。

复制代码

F = new NativeObject();
 
// 属性[[Class]] is "Function"
F.[[Class]] = "Function"
 
// 函数对象的原型
F.[[Prototype]] = Function.prototype
 
// 对函数自身的引用
// [[Call]] is activated by call expression F()
// 建立一个新的上下文
F.[[Call]] = <reference to function>
 
// built in general constructor of objects 内置构造器
// [[Construct]] is activated via "new" keyword [[Construct]]是在new 关键字的时候激活。
// and it is the one who allocates memory for new 它会为新对象申请内存
// objects; then it calls F.[[Call]]
// to initialize created objects passing as
// "this" value newly created object
F.[[Construct]] = internalConstructor
 
// scope chain of the current context
// i.e. context which creates function F 当前上下文的做用域链
F.[[Scope]] = activeContext.Scope
// if this functions is created
// via new Function(...), then 若是是经过new 运算符来建立的,则
F.[[Scope]] = globalContext.Scope
 
// number of formal parameters 形参的个数
F.length = countParameters
 
// a prototype of created by F objects 经过F建立出来的原型
__objectPrototype = new Object();
__objectPrototype.constructor = F // {DontEnum}, is not enumerable in loops
F.prototype = __objectPrototype
 
return F

要注意的是,F.[[Prototype]]是函数(构造器)的原型,而F.prototype是经过该函数建立出来的对象的原型(由于一般对这两个概念都会混淆,在有些文章中会将F.prototype叫作“构造器的原型”,这是错误的)。

结论

本文介绍了不少关于函数的内容;不过在后面的关于对象和原型的文章中,还会提到函数做为构造器是如何工做的。

相关文章
相关标签/搜索