深切明白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中一个很主要的对象-函数。我们将细致解说一下种种范例的函数是怎样影响高低文的变量对象以及每一个函数的作用域链都包含什么,我们将回复诸如像下面如许的题目:下面声明的函数有什么辨别么?(假如有,辨别是什么)。

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?”:

或许,下面的函数挪用,为何要用括号围困起来。

(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.

函数范例
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叫做“组织器的原型”,这是毛病的)。

结论

本文引见了许多关于函数的内容;不过在背面的关于对象和原型的文章中,还会提到函数作为组织器是怎样事情的。

    原文作者:mengera88
    原文地址: https://segmentfault.com/a/1190000003094971
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞