寫在前面的話:
試圖翻譯自 http://jibbering.com/faq/faq_notes/closures.html
文中大量提到《ECMA 262 》,我也沒時(shí)間讀這東西,可能有問題理解有誤。希望糾正。
只譯了前邊部分,我得理解幾天再繼續(xù)下去。
英文水平差,湊合看吧。
國內(nèi)找了半天沒這篇文章中文版,獻(xiàn)丑了就。
讀后有種豁然開朗的感覺,清楚了很多javascript的問題。
一、Introduction
Closure (閉包)
A
"closure" is an expression (typically a function) that can have free
variables together with an environment that binds those variables (that
"closes" the expression).
???
閉包是ECMAScript(javascript)語言強(qiáng)大的特征之一,如果沒有真正的理解它的概念,不可能很好使用它。在一般瀏覽器環(huán)境中,它們很容
易被建立,但也會造成比較難理解的代碼邏輯。為了避免閉包引起的缺點(diǎn),利用它所提供的優(yōu)點(diǎn),明白它的機(jī)制是重要的。javascript語言的閉包很大程
度上依靠 scope chains(函數(shù),變量的范圍鏈) 和 javascript對象的靈活的屬性機(jī)制 實(shí)現(xiàn)。
???
閉包簡單的解釋是,ECMAScript允許inner
functions(嵌套函數(shù)):函數(shù)可以定義在另外一個(gè)函數(shù)里面(關(guān)于嵌套函數(shù)可以看看<javascript權(quán)威指南>)。這些內(nèi)部的函
數(shù)可以訪問outer
function(父函數(shù))的local變量,參數(shù),其它內(nèi)部函數(shù)。當(dāng)內(nèi)部函數(shù)被構(gòu)造,并可以在函數(shù)外被獲得(函數(shù)當(dāng)成返回值),這個(gè)內(nèi)部函數(shù)被在
outer
function返回后被執(zhí)行(在outer函數(shù)外執(zhí)行),那一個(gè)閉包形成了。(簡單的理解,function被當(dāng)成數(shù)據(jù)類型傳遞或動態(tài)執(zhí)行)。
inner function還有權(quán)利訪問 那些outer function(父函數(shù))的local變量,參數(shù),其它內(nèi)部函數(shù)。那些outer
function(父函數(shù))的local變量,參數(shù),其它內(nèi)部函數(shù)在outer function返回前就有值,并返回的inner
function需要改變這些值。
我估計(jì)以下代碼就是一個(gè)閉包。
?
??? 不幸的,完全明白閉包需要知道它背后的機(jī)制和一些技術(shù)細(xì)節(jié)。
二、The Resolution of Property Names on Objects (javascript對象的屬性)
???
ECMAScript認(rèn)可兩類對象,“Native Object”和“Host Object”,Host Object屬于Native
Object的子類,在(ECMA 262 3rd Ed Section 4.3)中叫"Built-in
Object"(內(nèi)置對象)。Native objects屬于語言級別,host objects被環(huán)境提供(瀏覽器等),例如,document
objects,DOM nodes等。
??? 關(guān)于對象屬性的存取,數(shù)據(jù)類型,原型對象prototype的使用,我這就不譯了。
??? 可以參見我的另一篇文章
三、Identifier Resolution, Execution Contexts and Scope Chains
1、The Execution Context
???
執(zhí)行環(huán)境上下文(Execution Context)是個(gè)抽象的概念,the ECMSScript specification (ECMA
262 3rd edition) to define the behaviour required of ECMAScript
implementations。規(guī)范沒有說 execution contexts 應(yīng)該怎樣實(shí)現(xiàn),但規(guī)范中提到execution
contexts是個(gè)關(guān)聯(lián)屬性結(jié)構(gòu),因此你可以假想為一個(gè)有屬性的對象,但不是公有的(public)。
???
所有的javascript代碼在一個(gè)execution context中執(zhí)行。Global代碼(.js文件中)在我叫做globla execution
context中執(zhí)行,每個(gè)函數(shù)的調(diào)用有個(gè)專屬的execution context。注意,用eval函數(shù)執(zhí)行的代碼有個(gè)獨(dú)特的execution
context.(原文中說eval函數(shù)沒常被程序員應(yīng)用,確實(shí)如果掌握閉包使用后,還是可以避免一些eval使用的)。在section 10.2
of ECMA 262 (3rd edition)中詳細(xì)講述的execution context.
???
當(dāng)一個(gè)函數(shù)被調(diào)用,那相應(yīng)的execution context被建立,如果另外的函數(shù)(或同一個(gè)函數(shù)遞歸調(diào)用),那新的execution
context被建立,直到函數(shù)return(對于遞歸調(diào)用,execution
context是獨(dú)立的)。因此,javascript代碼的執(zhí)行會建立很多的execution contexts.
??? 當(dāng)一個(gè)函數(shù)的execution context被建立(javascript中有g(shù)lobal和function兩種,eval沒討論),按照順序,有幾個(gè)事情要發(fā)生。
??
(1)在一個(gè)函數(shù)的execution context中,一個(gè)"Activation"對象被建立(我在其它文章中叫調(diào)用對象)。the
activation被另外規(guī)范解釋。你可以把它當(dāng)成一個(gè)對象,因?yàn)樗袑ο蟮膶傩裕皇且话銓ο螅鼪]有原型對象,并不能被javascript代
碼直接引用。
?? (2)建立一個(gè)arguments對象,它和數(shù)組類似,以整數(shù)為索引來訪問值,表示函數(shù)的參數(shù)。它有l(wèi)ength和callee屬性。這個(gè)arguments對象被當(dāng)成activation對象的屬性。在函數(shù)內(nèi)可以直接訪問得到。
??
(3)下一步,execution context被分配一個(gè)
scope屬性(scope chain后面講到,我們可以把scope理解成對象的一個(gè)scope屬性,值是scope chain)。一個(gè)scope由一列對象組成
(或叫chain)。每個(gè)函數(shù)對象也有由chain組成的scope屬性。函數(shù)的scope=Activation object+上級對象的scope的屬性.(這里的scope可以理解成servlet中的chain,一系列請求組
成的鏈。)
?? (4)Activation object的實(shí)例化。Activation object(調(diào)用對象)可以看作Variable(變量)。
function
fun(a,b){};fun('p'); a和b會當(dāng)成調(diào)用對象的屬性,但函數(shù)調(diào)用是參數(shù)不夠,b的值為undefined。如果函數(shù)內(nèi)有inner
function,當(dāng)成屬性賦值給調(diào)用對象。變量實(shí)例化最后把local variables(函數(shù)內(nèi)部聲名的變量) 當(dāng)成調(diào)用對象的參數(shù)。
調(diào)用對象的屬性 包括函數(shù)的參數(shù)、內(nèi)部變量。
?? (5)在函數(shù)內(nèi),local variables作為調(diào)用對象的屬性出現(xiàn),function (a){alert(s);?? var s='a';}調(diào)用時(shí),s的值是unidefine,直到執(zhí)行到賦值語句后才有值。
?? (6)arguments屬性是以索引標(biāo)識的參數(shù),它和顯示聲明的參數(shù)是重復(fù)的,值也相同。如果local變量的簽名和參數(shù)相同,那么它們?nèi)咭粋€(gè)變化,其它都會相應(yīng)改變值。見下例
function a(p){alert( arguments [0]);alert(p);var p=1;alert(p);alert( arguments [0]);};a(0);
?? (7)最后,為this關(guān)鍵字設(shè)置值。可能 new Function()的有些疑問。關(guān)于this關(guān)鍵字,感覺自己還沒有徹底理解。this關(guān)鍵字關(guān)聯(lián)于執(zhí)行時(shí)的作用域,而非定義時(shí)的作用域。(The this keyword is relative to the execution context, not the declaration context )
??? global execution context 的過程和上面有些不同,它沒有arguments也不需要定義Activation
object。global execution context也不需要scope chain,因?yàn)閟cope
chain只有一個(gè),就是global object.它的變量實(shí)例化過程和inner
function其實(shí)都是根變量和函數(shù),就是global對象的屬性。global execution
context用this應(yīng)用global對象,在瀏覽器中為window.
2、Scope chains and [[scope]]
???
The scope chain of the execution context for a function call is
constructed by adding the execution context's Activation/Variable
object to the front of the scope chain held in the function object's
[[scope]] property。我理解每個(gè)函數(shù)執(zhí)行環(huán)境都有scope chain,子函數(shù)(inner function)的scope
chain包括它的父函數(shù)的scope chain,如此遞歸對global對象。
??? 在ECMAScript中,函數(shù)是個(gè)對象,它們可以用function聲明,或function表達(dá)式聲明,或Function構(gòu)造函數(shù)初始化。
??? 用Function構(gòu)造的函數(shù)對象一直有個(gè)scope屬性,指向的scope chain 僅包括 global 對象。
??? 用function表達(dá)式定義的函數(shù)對象,這類函數(shù)對象的scope chain被分配到內(nèi)部的scope 屬性。
(1)簡單的global函數(shù),例如:-
function exampleFunction(formalParameter){
??? ...?? // function body code
}
在global
execution context的變量實(shí)例化階段,the corresponding function object 被創(chuàng)建。global
execution context有scope chain,只包含global object.因此,函數(shù)對象被分配一個(gè)指向global
object的 scope屬性( internal [[scope]] property)。
(2)A similar scope chain is assigned when a function expression is executed in the global context:-
var exampleFuncRef = function(){
??? ...?? // function body code
}
這個(gè)例子scope chain情形與上類似。有個(gè)區(qū)別是函數(shù)對象在代碼執(zhí)行過程才創(chuàng)建。(見我以前文章)
(3)inner 函數(shù)的情形較為復(fù)雜,看下面代碼:
function exampleOuterFunction(formalParameter){
??? function exampleInnerFuncitonDec(){
??????? ... // inner function body
??? }
??? ...? // the rest of the outer function body.
}
exampleOuterFunction( 5 );
??? outer函數(shù)在global execution context變量實(shí)例化階段被創(chuàng)建,因此它的scope chain只包括global object.
??
當(dāng)global代碼執(zhí)行到調(diào)用exampleOuterFunction時(shí),一個(gè)新的execution
context被創(chuàng)建,(Activation)調(diào)用對象也被創(chuàng)建。這個(gè)新的execution context的scope
chain由兩部分組成,新的調(diào)用對象在頂層,outer函數(shù)scope chain(只包括global
object)在后。新的execution context的變量實(shí)例化階段(outer
函數(shù)體內(nèi))導(dǎo)致inner函數(shù)對象被創(chuàng)建,這個(gè)inner函數(shù)對象的[[scope]] property 被指向上述的哪個(gè)scope
chain,也就是調(diào)用對象和global object.注意inner function也有調(diào)用對象。
引用了 http://wj.cnblogs.com/archive/2006/04/22/381851.html 回復(fù)內(nèi)的代碼
<SCRIPT LANGUAGE="JavaScript">
<!--
//global 代碼實(shí)例化階段,它知道global object.
function createAClosure()
{
//當(dāng)調(diào)用時(shí),調(diào)用對象創(chuàng)建,execution context的scope chain 包括調(diào)用對象和global
//object.
var local = 0;
return function(){return ++local;}; //這個(gè)inner function 的scope //chain持有
//createAClosure的調(diào)用對象,所以也持有l(wèi)ocal的值
}
var c1 = createAClosure(); //調(diào)用對象和global object
var c2 = createAClosure(); //另外一個(gè)調(diào)用對象和global object
document.write(c1() + "<br/>");
document.write(c1() + "<br/>");
document.write(c1() + "<br/>");
document.write(c2() + "<br/>");
document.write(c2() + "<br/>");
//-->
</SCRIPT>
以上所有過程自動進(jìn)行,代碼不需要任何設(shè)置(造成很多人不知道閉包原因)。
scope chain 簡單看來可以按照下面的代碼來描述:
函數(shù)體外Execution context 的scope chain? 只有 global.
function fun(){
?函數(shù)體內(nèi)Execution context 的scope chain? fun的調(diào)用對象+global
??? function innerfun(){
????? inner函數(shù)體內(nèi)Execution context 的scope chain innerfun的調(diào)用對象 + fun的調(diào)用對象 + global
??? }
}
但是ECMAScript提供的with表達(dá)式會修改scope chain.with表達(dá)式,我是能不用就不用了,<javascript權(quán)威指南>中也說with會造成性能的集聚下降。原文貼在下面。有時(shí)間再仔細(xì)研究。
The
with statement evaluates an expression and if that expression is an
object it is added to the scope chain of the current execution context
(in front of the Activation/Variable object). The with statement then
executes another statement (that may itself be a block statement) and
then restores the execution context's scope chain to what it was
before.
A function declaration could not be affected by a
with statement as they result in the creation of function objects
during variable instantiation, but a function expression can be
evaluated inside a with statement:-
/* create a global variable - y - that refers to an object:- */
var y = {x:5}; // object literal with an - x - property
function exampleFuncWith(){
??? var z;
??? /* Add the object referred to by the global variable - y - to the
?????? front of he scope chain:-
??? */
??? with(y){
??????? /* evaluate a function expression to create a function object
?????????? and assign a reference to that function object to the local
?????????? variable - z - :-
??????? */
??????? z = function(){
??????????? ... // inner function expression body;
??????? }
??? }
??? ...
}
/* execute the - exampleFuncWith - function:- */
exampleFuncWith();
When
the exampleFuncWith function is called the resulting execution context
has a scope chain consisting of its Activation object followed by the
global object. The execution of the with statement adds the object
referred to by the global variable y to the front of that scope chain
during the evaluation of the function expression. The function object
created by the evaluation of the function expression is assigned a
[[scope]] property that corresponds with the scope of the execution
context in which it is created. A scope chain consisting of object y
followed by the Activation object from the execution context of the
outer function call, followed by the global object.
When
the block statement associated with the with statement terminates the
scope of the execution context is restored (the y object is removed),
but the function object has been created at that point and its
[[scope]] property assigned a reference to a scope chain with the y
object at its head.
3、Identifier Resolution
?? 關(guān)于這部分我決定不按照原文直譯。Identifier Resolution是一個(gè)過程,而不是具體的概念,我舉個(gè)例子可能就明白了。
<SCRIPT LANGUAGE="JavaScript">
<!--
var s_global='global';//scope chain {global} 中
var s_outer='global';//scope chain {global} 中
var s_inner='global';//scope chain {global} 中
function outerfun(){//scope chain {global} 中
??? var s_outer='outer';//scope chain? {outerfun調(diào)用對象,global}
?pf('outer代碼開始');
?pf(s_global);//global
??? pf(s_outer);//outerfun調(diào)用對象
??? pf(s_inner);//global
?function innerfun(){////scope chain? {outerfun調(diào)用對象,global}
??? var s_inner='inner';//scope chain? {innerfun調(diào)用對象,outerfun調(diào)用對象,global}
?pf('inner代碼開始');
?pf(s_global);//global
??? pf(s_outer);//outerfun調(diào)用對象
??? pf(s_inner);//innerfun調(diào)用對象
?}
?return innerfun;
}
function pf(msg){document.writeln('</br>'+msg);};
pf('global代碼開始');
pf(s_global);//global
pf(s_outer);//global
pf(s_inner);//global
var a=outerfun();
a();
pf('第二個(gè)函數(shù)開始------------------------');
var b=outerfun();
b();
//-->
</SCRIPT>
其
實(shí)Identifier Resolution就是屬性查找的過程。 先從scope chain 的第一個(gè)對象開始找,如果找不到再從scope
chain的第二個(gè)對象找, global對象始終是scope chain 的最后一個(gè)對象,如果global
object中也找不到屬性,那為undefined.
有兩個(gè)注意點(diǎn):
?? 如果可能,這個(gè)查找過程會對對象的prototype(原型對象)查找。先找實(shí)例屬性,再找原型屬性。見我的其它文章。
?? 在函數(shù)內(nèi),這個(gè)函數(shù)的調(diào)用對象包括的參數(shù),local變量,inner函數(shù)等。
如果有對javascript語言感興趣的,歡迎交流批評。
http://www.tkk7.com/zkjbeyond/category/10156.html
參考:
??? 《javascript權(quán)威指南》
???? http://jibbering.com/faq/faq_notes/closures.html