摘要:注意,下面一個立即執(zhí)行的函數(shù),周圍的括號不是必須的,因為函數(shù)已經(jīng)處在表達式的位置,解析器知道它處理的是在函數(shù)執(zhí)行階段應該被創(chuàng)建的,這樣在函數(shù)創(chuàng)建后立即調用了函數(shù)。
本文是翻譯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:
在這一章節(jié)中,我們來探討下ECMAScript中一個很重要的對象-函數(shù)。我們將詳細講解一下各種類型的函數(shù)是如何影響上下文的變量對象以及每個函數(shù)的作用域鏈都包含什么,我們將回答諸如像下面這樣的問題:下面聲明的函數(shù)有什么區(qū)別么?(如果有,區(qū)別是什么)。
var foo = function () { ... };
from functions defined in a “habitual” way?”:
傳統(tǒng)的函數(shù)聲明是:
function foo() { ... }
Or, “why in the next call, the function has to be surrounded with parentheses?”:
或者,下面的函數(shù)調用,為什么要用括號包圍起來。
(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.
函數(shù)類型
In ECMAScript there are three function types and each of them has its own features.
在ECMAScript中,有三種不同的函數(shù)類型,并且他們都有自己的特點。
函數(shù)聲明
A Function Declaration (abbreviated form is FD) is a function which:
函數(shù)聲明(簡寫FD)是這樣的一個函數(shù)
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),要么處于其它函數(shù)的主體(FunctionBody)中
在進入上下文階段創(chuàng)建
影響變量對象
以下面的方式聲明
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).
這種類型的函數(shù)最重要的特點就是它影響變量對象(存儲在變量對象的上下文中),這個特性也說明了第二個很重要的觀點(它是變量對象特性的結果)在代碼執(zhí)行階段它們已經(jīng)可用(因為FD在進入上下文階段已經(jīng)存在于VO中——代碼執(zhí)行之前)。
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):
另外一個重點知識點是上述定義中的第二點——函數(shù)聲明在源碼中的位置:
// 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個位置可以聲明函數(shù),也就是說:不可能在表達式位置或一個代碼塊中定義它。
另外一種可以取代函數(shù)聲明的方式是函數(shù)表達式,解釋如下:
函數(shù)表達式
A Function Expression (abbreviated form is FE) is a function which:
函數(shù)表達式(簡寫FE)是這樣的一個函數(shù)
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.
在源碼中須出現(xiàn)在表達式的位置
有可選的名稱
不會影響變量對象
在代碼執(zhí)行階段創(chuàng)建
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:
這種函數(shù)類型的主要特點在于它在源碼中總是處在表達式的位置。最簡單的一個例子就是一個賦值聲明:
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:
該例演示是讓一個匿名函數(shù)表達式賦值給變量foo,然后該函數(shù)可以用foo這個名稱進行訪問——foo()。
同時和定義里描述的一樣,函數(shù)表達式也可以擁有可選的名稱:
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(),而在函數(shù)內(nèi)部(如遞歸調用),有可能使用名稱“_foo”。
如果FE有一個名稱,就很難與FD區(qū)分。但是,如果你明白定義,區(qū)分起來就簡單明了: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只能在代碼執(zhí)行階段創(chuàng)建而且不存在于變量對象中,讓我們來看一個示例行為:
// 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
相當一部分問題出現(xiàn)了,我們?yōu)槭裁葱枰瘮?shù)表達式?答案是很顯然的——在表達式中使用它們,”不會污染”變量對象。最簡單的例子是將一個函數(shù)作為參數(shù)傳遞給其它函數(shù)。
function foo(callback) { callback(); } foo(function bar() { alert("foo.bar"); }); foo(function baz() { alert("foo.baz"); });
在上述例子里,F(xiàn)E賦值給了一個變量(也就是參數(shù)),函數(shù)將該表達式保存在內(nèi)存中,并通過變量名來訪問(因為變量影響變量對象),如下:
var foo = function () { alert("foo"); }; foo();
另外一個例子是創(chuàng)建封裝的閉包從外部上下文中隱藏輔助性數(shù)據(jù)(在下面的例子中我們使用FE,它在創(chuàng)建后立即調用):
var foo = {}; (function initialize() { var x = 10; foo.bar = function () { alert(x); }; })(); foo.bar(); // 10; alert(x); // "x" is not defined
我們看到函數(shù)foo.bar(通過[[Scope]]屬性)訪問到函數(shù)initialize的內(nèi)部變量“x”。同時,“x”在外部不能直接訪問。在許多庫中,這種策略常用來創(chuàng)建”私有”數(shù)據(jù)和隱藏輔助實體。在這種模式中,初始化的FE的名稱通常被忽略:
(function () { // initializing scope })();
還有一個例子是:在代碼執(zhí)行階段通過條件語句進行創(chuàng)建FE,不會污染變量對象VO。
var foo = 10; var bar = (foo % 2 == 0 ? function () { alert(0); } : function () { alert(1); } ); bar(); // 0
關于圓括號的問題
讓我們回頭并回答在文章開頭提到的問題——”為何在函數(shù)創(chuàng)建后的立即調用中必須用圓括號來包圍它?”,答案就是:表達式句子的限制就是這樣的。
根據(jù)標準,表達式語句不能以一個大括號{開始是因為他很難與代碼塊區(qū)分,同樣,他也不能以函數(shù)關鍵字開始,因為很難與函數(shù)聲明進行區(qū)分。即,所以,如果我們定義一個立即執(zhí)行的函數(shù),在其創(chuàng)建后立即按以下方式調用:
function () { ... }(); // or even with a name function foo() { ... }();
我們使用了函數(shù)聲明,上述2個定義,解釋器在解釋的時候都會報錯,但是可能有多種原因。
如果在全局代碼里定義(也就是程序級別),解釋器會將它看做是函數(shù)聲明,因為他是以function關鍵字開頭,第一個例子,我們會得到SyntaxError錯誤,是因為函數(shù)聲明沒有名字(我們前面提到了函數(shù)聲明必須有名字)。
第二個例子,我們有一個名稱為foo的一個函數(shù)聲明正常創(chuàng)建,但是我們依然得到了一個語法錯誤——沒有任何表達式的分組操作符錯誤。在函數(shù)聲明后面他確實是一個分組操作符,而不是一個函數(shù)調用所使用的圓括號。所以如果我們聲明如下代碼:
// "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
上述代碼是沒有問題的,因為聲明的時候產(chǎn)生了2個對象:一個函數(shù)聲明,一個帶有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");
根據(jù)規(guī)范,上述代碼是錯誤的(一個表達式語句不能以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.
我們?nèi)绻麃砀嬖V解釋器:我就像在函數(shù)聲明之后立即調用,答案是很明確的,你得聲明函數(shù)表達式function expression,而不是函數(shù)聲明function declaration,并且創(chuàng)建表達式最簡單的方式就是用分組操作符括號,里邊放入的永遠是表達式,所以解釋器在解釋的時候就不會出現(xiàn)歧義。在代碼執(zhí)行階段這個的function就會被創(chuàng)建,并且立即執(zhí)行,然后自動銷毀(如果沒有引用的話)。
(function foo(x) { alert(x); })(1); // OK, it"s a call, not a grouping operator, 1
上述代碼就是我們所說的在用括號括住一個表達式,然后通過(1)去調用。
注意,下面一個立即執(zhí)行的函數(shù),周圍的括號不是必須的,因為函數(shù)已經(jīng)處在表達式的位置,解析器知道它處理的是在函數(shù)執(zhí)行階段應該被創(chuàng)建的FE,這樣在函數(shù)創(chuàng)建后立即調用了函數(shù)。
var foo = { bar: function (x) { return x % 2 != 0 ? "yes" : "no"; }(1) }; alert(foo.bar); // "yes"
就像我們看到的,foo.bar是一個字符串而不是一個函數(shù),這里的函數(shù)僅僅用來根據(jù)條件參數(shù)初始化這個屬性——它創(chuàng)建后并立即調用。
因此,”關于圓括號”問題完整的答案如下:當函數(shù)不在表達式的位置的時候,分組操作符圓括號是必須的——也就是手工將函數(shù)轉化成FE。如果解析器知道它處理的是FE,就沒必要用圓括號
Apart from surrounding parentheses it is possible to use any other way of transformation of a function to FE type. For example:
除了大括號以外,如下形式也可以將函數(shù)轉化為FE類型,例如:
1,
function () { alert("anonymous function is called"); }(); // or this one !function () { alert("ECMAScript"); }(); // and any other manual // transformation
...
但是,在這個例子中,圓括號是最簡潔的方式。
順便提一句,組表達式包圍函數(shù)描述可以沒有調用圓括號,也可包含調用圓括號,即,下面的兩個表達式都是正確的FE。
(function () {})(); (function () {}());
實現(xiàn)擴展: 函數(shù)語句
下面的代碼,根據(jù)貴方任何一個function聲明都不應該被執(zhí)行:
if (true) { function foo() { alert(0); } } else { function foo() { alert(1); } } foo(); // 1 or 0 ? test in different implementations
這里有必要說明的是,按照標準,這種句法結構通常是不正確的,因為我們還記得,一個函數(shù)聲明(FD)不能出現(xiàn)在代碼塊中(這里if和else包含代碼塊)。我們曾經(jīng)講過,F(xiàn)D僅出現(xiàn)在兩個位置:程序級(Program level)或直接位于其它函數(shù)體中。
因為代碼塊僅包含語句,所以這是不正確的。可以出現(xiàn)在塊中的函數(shù)的唯一位置是這些語句中的一個——上面已經(jīng)討論過的表達式語句。但是,按照定義它不能以大括號開始(既然它有別于代碼塊)或以一個函數(shù)關鍵字開始(既然它有別于FD)。
但是,在標準的錯誤處理章節(jié)中,它允許程序語法的擴展執(zhí)行。這樣的擴展之一就是我們見到的出現(xiàn)在代碼塊中的函數(shù)。在這個例子中,現(xiàn)今的所有存在的執(zhí)行都不會拋出異常,都會處理它。但是它們都有自己的方式。
if-else分支語句的出現(xiàn)意味著一個動態(tài)的選擇。即,從邏輯上來說,它應該是在代碼執(zhí)行階段動態(tài)創(chuàng)建的函數(shù)表達式(FE)。但是,大多數(shù)執(zhí)行在進入上下文階段時簡單的創(chuàng)建函數(shù)聲明(FD),并使用最后聲明的函數(shù)。即,函數(shù)foo將顯示”1″,事實上else分支將永遠不會執(zhí)行。
但是,SpiderMonkey (和TraceMonkey)以兩種方式對待這種情況:一方面它不會將函數(shù)作為聲明處理(即,函數(shù)在代碼執(zhí)行階段根據(jù)條件創(chuàng)建),但另一方面,既然沒有括號包圍(再次出現(xiàn)解析錯誤——”與FD有別”),他們不能被調用,所以也不是真正的函數(shù)表達式,它儲存在變量對象中。
我個人認為這個例子中SpiderMonkey 的行為是正確的,拆分了它自身的函數(shù)中間類型——(FE+FD)。這些函數(shù)在合適的時間創(chuàng)建,根據(jù)條件,也不像FE,倒像一個可以從外部調用的FD,SpiderMonkey將這種語法擴展 稱之為函數(shù)語句(縮寫為FS);該語法在MDC中提及過。
命名函數(shù)表達式的特性當函數(shù)表達式FE有一個名稱(稱為命名函數(shù)表達式,縮寫為NFE)時,將會出現(xiàn)一個重要的特點。從定義(正如我們從上面示例中看到的那樣)中我們知道函數(shù)表達式不會影響一個上下文的變量對象(那樣意味著既不可能通過名稱在函數(shù)聲明之前調用它,也不可能在聲明之后調用它)。但是,F(xiàn)E在遞歸調用中可以通過名稱調用自身。
(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”。在上下文的父變量對象中創(chuàng)建foo?也不是,因為按照定義——FE不會影響VO(變量對象)——從外部調用foo我們可以實實在在的看到。那么在哪里呢?
以下是關鍵點。當解釋器在代碼執(zhí)行階段遇到命名的FE時,在FE創(chuàng)建之前,它創(chuàng)建了輔助的特定對象,并添加到當前作用域鏈的最前端。然后它創(chuàng)建了FE,此時(正如我們在第四章 作用域鏈知道的那樣)函數(shù)獲取了[[Scope]] 屬性——創(chuàng)建這個函數(shù)上下文的作用域鏈)。此后,F(xiàn)E的名稱添加到特定對象上作為唯一的屬性;這個屬性的值是引用到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
因此,在函數(shù)外部這個名稱不可用的(因為它不在父作用域鏈中),但是,特定對象已經(jīng)存儲在函數(shù)的[[scope]]中,在那里名稱是可用的。
但是需要注意的是一些實現(xiàn)(如Rhino)不是在特定對象中而是在FE的激活對象中存儲這個可選的名稱。Microsoft 中的執(zhí)行完全打破了FE規(guī)則,它在父變量對象中保持了這個名稱,這樣函數(shù)在外部變得可以訪問。
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.
說到實現(xiàn),部分版本的SpiderMonkey有一個與上述提到的特殊對象相關的特性,這個特性也可以看作是個bug(既然所有的實現(xiàn)都是嚴格遵循標準的,那么這個就是標準的問題了)。 此特性和標識符處理相關: 作用域鏈的分析是二維的,在標識符查詢的時候,還要考慮作用域鏈中每個對象的原型鏈。
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對象上定義一個屬性,并將該屬性值指向一個“根本不存在”的變量時,就能夠體現(xiàn)該特性。 比如,如下例子中的變量“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:
活躍對象是沒有原型一說的。可以通過內(nèi)部函數(shù)還證明。 如果在定義一個局部變量“x”并聲明一個內(nèi)部函數(shù)(FD或者匿名的FE),然后,在內(nèi)部函數(shù)中引用變量“x”,這個時候該變量會在上層函數(shù)上下文中查詢到(理應如此),而不是在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:
在有些實現(xiàn)中,存在這樣的異常:它們會在活躍對象設置原型。比方說,在Blackberry的實現(xiàn)中,上述例子中變量“x”值就會變成10。 因為,“x”從Object.prototype中就找到了:
AO(bar FD or anonymous FE) -> no -> AO(bar FD or anonymous FE).[[Prototype]] -> yes - 10
當出現(xiàn)有名字的FE的特殊對象的時候,在SpiderMonkey中也是有同樣的異常。該特殊對象是常見對象 —— “和通過new Object()表達式產(chǎn)生的一樣”。 相應地,它也應當繼承自Object.prototype,上述描述只針對SpiderMonkey(1.7版本)。其他的實現(xiàn)(包括新的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:
微軟的實現(xiàn)——JScript,是IE的JS引擎(截至本文撰寫時最新是JScript5.8——IE8),該引擎與NFE相關的bug有很多。每個bug基本上都和ECMA-262-3rd標準是完全違背的。 有些甚至會引發(fā)嚴重的錯誤。
第一,針對上述這樣的情況,JScript完全破壞了FE的規(guī)則:不應當將函數(shù)名字保存在變量對象中的。 另外,F(xiàn)E的名字應當保存在特殊對象中,并且只有在函數(shù)自身內(nèi)部才可以訪問(其他地方均不可以)。而JScript卻將其直接保存在上層上下文的變量對象中。 并且,JScript居然還將FE以FD的方式處理,在進入上下文的時候就將其創(chuàng)建出來,并在定義之前就可以訪問到:
// 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的規(guī)則。
第二,在聲明同時,將NFE賦值給一個變量的時候,JScript會創(chuàng)建兩個不同的函數(shù)對象。 這種行為感覺完全不符合邏輯(特別是考慮到在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
這個時候就好解釋了。實施上,一開始的確創(chuàng)建了兩個對象,不過之后就只剩下一個了。這里將NFE以FD的方式來處理,然后,當進入上下文的時候,F(xiàn)D bar就創(chuàng)建出來了。 在這之后,到了執(zhí)行代碼階段,又創(chuàng)建出了第二個對象 —— FE bar,該對象不會進行保存。相應的,由于沒有變量對其進行引用,隨后FE bar對象就被移除了。 因此,這里就只剩下一個對象——FD bar對象,對該對象的引用就賦值給了foo變量。
第三,通過arguments.callee對一個函數(shù)進行間接引用,它引用的是和激活函數(shù)名一致的對象(事實上是——函數(shù),因為有兩個對象):
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來處理,但當遇到條件語句又不遵循此規(guī)則了。比如說,和FD那樣,NFE會在進入上下文的時候就創(chuàng)建出來,這樣最后一次定義的就會被使用:
var foo = function bar() { alert(1); }; if (false) { foo = function bar() { alert(2); }; } bar(); // 2 foo(); // 1
上述行為從邏輯上也是可以解釋通的: 當進入上下文的時候,最后一次定義的FD bar被創(chuàng)建出來(有alert(2)的函數(shù)), 之后到了執(zhí)行代碼階段又一個新的函數(shù) —— FE bar被創(chuàng)建出來,對其引用賦值給了變量foo。因此(if代碼塊中由于判斷條件是false,因此其代碼塊中的代碼永遠不會被執(zhí)行到)foo函數(shù)的調用會打印出1。 盡管“邏輯上”是對的,但是這個仍然算是IE的bug。因為它明顯就破壞了實現(xiàn)的規(guī)則,所以我這里用了引號“邏輯上”。
第五個JScript中NFE的bug和通過給一個未受限的標識符賦值(也就是說,沒有var關鍵字)來創(chuàng)建全局對象的屬性相關。 由于這里NFE會以FD的方式來處理,并相應地會保存在變量對象上,賦值給未受限的標識符(不是給變量而是給全局對象的一般屬性), 當函數(shù)名和標識符名字相同的時候,該屬性就不會是全局的了。
(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 “l(fā)ogic” 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.
這里從“邏輯上”又是可以解釋通的: 進入上下文時,函數(shù)聲明在匿名函數(shù)本地上下文的活躍對象中。 當進入執(zhí)行代碼階段的時候,因為foo這個名字已經(jīng)在AO中存在了(本地),相應地,賦值操作也只是簡單的對AO中的foo進行更新而已。 并沒有在全局對象上創(chuàng)建新的屬性。
通過Function構造器創(chuàng)建的函數(shù)
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:
這類函數(shù)有別于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函數(shù)的[[Scope]]屬性并未包含foo上下文的AO —— 變量“y”是無法訪問的,并且變量“x”是來自全局上下文。 順便提下,這里要注意的是,F(xiàn)unction構造器可以通過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:
此類函數(shù)其他特性則和同類語法產(chǎn)生式以及聯(lián)合對象有關。 該機制在標準中建議在作優(yōu)化的時候采用(當然,具體的實現(xiàn)者也完全有權利不使用這類優(yōu)化)。比方說,有100元素的數(shù)組,在循環(huán)數(shù)組過程中會給數(shù)組每個元素賦值(函數(shù)), 這個時候,實現(xiàn)的時候就可以采用聯(lián)合對象的機制了。這樣,最終所有的數(shù)組元素都會引用同一個函數(shù)(只有一個函數(shù)):
var a = []; for (var k = 0; k < 100; k++) { a[k] = function () {}; // possibly, joined objects are used }
但是,通過Function構造器創(chuàng)建的函數(shù)就無法使用聯(lián)合對象了:
var a = []; for (var k = 0; k < 100; k++) { a[k] = Function(""); // always 100 different funcitons }
下面是另外一個和聯(lián)合對象相關的例子:
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.
上述例子,在實現(xiàn)過程中同樣可以使用聯(lián)合對象。來使得x和y引用同一個對象,因為函數(shù)(包括它們內(nèi)部的[[Scope]]屬性)物理上是不可分辨的。 因此,通過Function構造器創(chuàng)建的函數(shù)總是會占用更多內(nèi)存資源。
函數(shù)創(chuàng)建的算法
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.
如下所示使用偽代碼表示的函數(shù)創(chuàng)建的算法(不包含聯(lián)合對象的步驟)。有助于理解ECMAScript中的函數(shù)對象。此算法對所有函數(shù)類型都是一樣的。
復制代碼
F = new NativeObject(); // 屬性[[Class]] is "Function" F.[[Class]] = "Function" // 函數(shù)對象的原型 F.[[Prototype]] = Function.prototype // 對函數(shù)自身的引用 // [[Call]] is activated by call expression F() // 創(chuàng)建一個新的上下文 F.[[Call]] =// built in general constructor of objects 內(nèi)置構造器 // [[Construct]] is activated via "new" keyword [[Construct]]是在new 關鍵字的時候激活。 // and it is the one who allocates memory for new 它會為新對象申請內(nèi)存 // 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 運算符來創(chuàng)建的,則 F.[[Scope]] = globalContext.Scope // number of formal parameters 形參的個數(shù) F.length = countParameters // a prototype of created by F objects 通過F創(chuàng)建出來的原型 __objectPrototype = new Object(); __objectPrototype.constructor = F // {DontEnum}, is not enumerable in loops F.prototype = __objectPrototype return F
要注意的是,F(xiàn).[[Prototype]]是函數(shù)(構造器)的原型,而F.prototype是通過該函數(shù)創(chuàng)建出來的對象的原型(因為通常對這兩個概念都會混淆,在有些文章中會將F.prototype叫做“構造器的原型”,這是錯誤的)。
結論
本文介紹了很多關于函數(shù)的內(nèi)容;不過在后面的關于對象和原型的文章中,還會提到函數(shù)作為構造器是如何工作的。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85871.html
摘要:深入淺出的理解問題的由來寫法一寫法二雖然和指向同一個函數(shù),但是執(zhí)行結果可能不一樣。該變量由運行環(huán)境提供。所以,就出現(xiàn)了,它的設計目的就是在函數(shù)體內(nèi)部,指代函數(shù)當前的運行環(huán)境。 深入淺出this的理解 問題的由來 var obj = { foo: function(){} } var foo = obj.foo; // 寫法一 obj.foo(); // 寫法二 foo...
摘要:下面是用實現(xiàn)轉成抽象語法樹如下還支持繼承以下是轉換結果最終的結果還是代碼,其中包含庫中的一些函數(shù)。可以使用新的易于使用的類定義,但是它仍然會創(chuàng)建構造函數(shù)和分配原型。 這是專門探索 JavaScript 及其所構建的組件的系列文章的第 15 篇。 想閱讀更多優(yōu)質文章請猛戳GitHub博客,一年百來篇優(yōu)質文章等著你! 如果你錯過了前面的章節(jié),可以在這里找到它們: JavaScript 是...
摘要:閉包面試題解由于作用域鏈機制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個值,這引起的一個副作用就是如果內(nèi)部函數(shù)在一個循環(huán)中,那么變量的值始終為最后一個值。 (關注福利,關注本公眾號回復[資料]領取優(yōu)質前端視頻,包括Vue、React、Node源碼和實戰(zhàn)、面試指導) 本周正式開始前端進階的第二期,本周的主題是作用域閉包,今天是第8天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了...
摘要:下面我們就羅列閉包的幾個常見問題,從回答問題的角度來理解和定義你們心中的閉包。函數(shù)可以通過作用域鏈相互關聯(lián)起來,函數(shù)內(nèi)部的變量可以保存在其他函數(shù)作用域內(nèi),這種特性在計算機科學文獻中稱為閉包。 寫這篇文章之前,我對閉包的概念及原理模糊不清,一直以來都是以通俗的外層函數(shù)包裹內(nèi)層....來欺騙自己。并沒有說這種說法的對與錯,我只是不想擁有從眾心理或者也可以說如果我們說出更好更低層的東西,逼格...
摘要:譯者注翻譯一個對新手比較友好的工作原理解析系列文章注意以下全部是概念經(jīng)驗豐富的老鳥可以離場啦正文從這里開始隨著的流行團隊們正在利用來支持多個級別的技術棧包括前端后端混合開發(fā)嵌入式設備以及更多這篇文章旨在成為深入挖掘和實際上他是怎么工作的系列 譯者注 翻譯一個對新手比較友好的 JavaScript 工作原理解析系列文章 注意: 以下全部是概念,經(jīng)驗豐富的老鳥可以離場啦 正文從這里開始 隨...
閱讀 2536·2023-04-25 19:47
閱讀 3383·2019-08-29 17:18
閱讀 856·2019-08-29 15:26
閱讀 3360·2019-08-29 14:17
閱讀 1116·2019-08-26 13:49
閱讀 3339·2019-08-26 13:22
閱讀 3023·2019-08-26 10:44
閱讀 2693·2019-08-23 16:51