寫在前面的話:
試圖翻譯自 http://jibbering.com/faq/faq_notes/closures.html
文中大量提到《ECMA 262 》,我也沒時間讀這東西,可能有問題理解有誤。希望糾正。
只譯了前邊部分,我得理解幾天再繼續下去。
英文水平差,湊合看吧。
國內找了半天沒這篇文章中文版,獻丑了就。
讀后有種豁然開朗的感覺,清楚了很多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)語言強大的特征之一,如果沒有真正的理解它的概念,不可能很好使用它。在一般瀏覽器環境中,它們很容
易被建立,但也會造成比較難理解的代碼邏輯。為了避免閉包引起的缺點,利用它所提供的優點,明白它的機制是重要的。javascript語言的閉包很大程
度上依靠 scope chains(函數,變量的范圍鏈) 和 javascript對象的靈活的屬性機制 實現。
???
閉包簡單的解釋是,ECMAScript允許inner
functions(嵌套函數):函數可以定義在另外一個函數里面(關于嵌套函數可以看看<javascript權威指南>)。這些內部的函
數可以訪問outer
function(父函數)的local變量,參數,其它內部函數。當內部函數被構造,并可以在函數外被獲得(函數當成返回值),這個內部函數被在
outer
function返回后被執行(在outer函數外執行),那一個閉包形成了。(簡單的理解,function被當成數據類型傳遞或動態執行)。
inner function還有權利訪問 那些outer function(父函數)的local變量,參數,其它內部函數。那些outer
function(父函數)的local變量,參數,其它內部函數在outer function返回前就有值,并返回的inner
function需要改變這些值。
我估計以下代碼就是一個閉包。
?
??? 不幸的,完全明白閉包需要知道它背后的機制和一些技術細節。
二、The Resolution of Property Names on Objects (javascript對象的屬性)
???
ECMAScript認可兩類對象,“Native Object”和“Host Object”,Host Object屬于Native
Object的子類,在(ECMA 262 3rd Ed Section 4.3)中叫"Built-in
Object"(內置對象)。Native objects屬于語言級別,host objects被環境提供(瀏覽器等),例如,document
objects,DOM nodes等。
??? 關于對象屬性的存取,數據類型,原型對象prototype的使用,我這就不譯了。
??? 可以參見我的另一篇文章
三、Identifier Resolution, Execution Contexts and Scope Chains
1、The Execution Context
???
執行環境上下文(Execution Context)是個抽象的概念,the ECMSScript specification (ECMA
262 3rd edition) to define the behaviour required of ECMAScript
implementations。規范沒有說 execution contexts 應該怎樣實現,但規范中提到execution
contexts是個關聯屬性結構,因此你可以假想為一個有屬性的對象,但不是公有的(public)。
???
所有的javascript代碼在一個execution context中執行。Global代碼(.js文件中)在我叫做globla execution
context中執行,每個函數的調用有個專屬的execution context。注意,用eval函數執行的代碼有個獨特的execution
context.(原文中說eval函數沒常被程序員應用,確實如果掌握閉包使用后,還是可以避免一些eval使用的)。在section 10.2
of ECMA 262 (3rd edition)中詳細講述的execution context.
???
當一個函數被調用,那相應的execution context被建立,如果另外的函數(或同一個函數遞歸調用),那新的execution
context被建立,直到函數return(對于遞歸調用,execution
context是獨立的)。因此,javascript代碼的執行會建立很多的execution contexts.
??? 當一個函數的execution context被建立(javascript中有global和function兩種,eval沒討論),按照順序,有幾個事情要發生。
??
(1)在一個函數的execution context中,一個"Activation"對象被建立(我在其它文章中叫調用對象)。the
activation被另外規范解釋。你可以把它當成一個對象,因為它有對象的屬性,但它不是一般對象,它沒有原型對象,并不能被javascript代
碼直接引用。
?? (2)建立一個arguments對象,它和數組類似,以整數為索引來訪問值,表示函數的參數。它有length和callee屬性。這個arguments對象被當成activation對象的屬性。在函數內可以直接訪問得到。
??
(3)下一步,execution context被分配一個
scope屬性(scope chain后面講到,我們可以把scope理解成對象的一個scope屬性,值是scope chain)。一個scope由一列對象組成
(或叫chain)。每個函數對象也有由chain組成的scope屬性。函數的scope=Activation object+上級對象的scope的屬性.(這里的scope可以理解成servlet中的chain,一系列請求組
成的鏈。)
?? (4)Activation object的實例化。Activation object(調用對象)可以看作Variable(變量)。
function
fun(a,b){};fun('p'); a和b會當成調用對象的屬性,但函數調用是參數不夠,b的值為undefined。如果函數內有inner
function,當成屬性賦值給調用對象。變量實例化最后把local variables(函數內部聲名的變量) 當成調用對象的參數。
調用對象的屬性 包括函數的參數、內部變量。
?? (5)在函數內,local variables作為調用對象的屬性出現,function (a){alert(s);?? var s='a';}調用時,s的值是unidefine,直到執行到賦值語句后才有值。
?? (6)arguments屬性是以索引標識的參數,它和顯示聲明的參數是重復的,值也相同。如果local變量的簽名和參數相同,那么它們三者一個變化,其它都會相應改變值。見下例
function a(p){alert( arguments [0]);alert(p);var p=1;alert(p);alert( arguments [0]);};a(0);
?? (7)最后,為this關鍵字設置值。可能 new Function()的有些疑問。關于this關鍵字,感覺自己還沒有徹底理解。this關鍵字關聯于執行時的作用域,而非定義時的作用域。(The this keyword is relative to the execution context, not the declaration context )
??? global execution context 的過程和上面有些不同,它沒有arguments也不需要定義Activation
object。global execution context也不需要scope chain,因為scope
chain只有一個,就是global object.它的變量實例化過程和inner
function其實都是根變量和函數,就是global對象的屬性。global execution
context用this應用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。我理解每個函數執行環境都有scope chain,子函數(inner function)的scope
chain包括它的父函數的scope chain,如此遞歸對global對象。
??? 在ECMAScript中,函數是個對象,它們可以用function聲明,或function表達式聲明,或Function構造函數初始化。
??? 用Function構造的函數對象一直有個scope屬性,指向的scope chain 僅包括 global 對象。
??? 用function表達式定義的函數對象,這類函數對象的scope chain被分配到內部的scope 屬性。
(1)簡單的global函數,例如:-
function exampleFunction(formalParameter){
??? ...?? // function body code
}
在global
execution context的變量實例化階段,the corresponding function object 被創建。global
execution context有scope chain,只包含global object.因此,函數對象被分配一個指向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
}
這個例子scope chain情形與上類似。有個區別是函數對象在代碼執行過程才創建。(見我以前文章)
(3)inner 函數的情形較為復雜,看下面代碼:
function exampleOuterFunction(formalParameter){
??? function exampleInnerFuncitonDec(){
??????? ... // inner function body
??? }
??? ...? // the rest of the outer function body.
}
exampleOuterFunction( 5 );
??? outer函數在global execution context變量實例化階段被創建,因此它的scope chain只包括global object.
??
當global代碼執行到調用exampleOuterFunction時,一個新的execution
context被創建,(Activation)調用對象也被創建。這個新的execution context的scope
chain由兩部分組成,新的調用對象在頂層,outer函數scope chain(只包括global
object)在后。新的execution context的變量實例化階段(outer
函數體內)導致inner函數對象被創建,這個inner函數對象的[[scope]] property 被指向上述的哪個scope
chain,也就是調用對象和global object.注意inner function也有調用對象。
引用了 http://wj.cnblogs.com/archive/2006/04/22/381851.html 回復內的代碼
<SCRIPT LANGUAGE="JavaScript">
<!--
//global 代碼實例化階段,它知道global object.
function createAClosure()
{
//當調用時,調用對象創建,execution context的scope chain 包括調用對象和global
//object.
var local = 0;
return function(){return ++local;}; //這個inner function 的scope //chain持有
//createAClosure的調用對象,所以也持有local的值
}
var c1 = createAClosure(); //調用對象和global object
var c2 = createAClosure(); //另外一個調用對象和global object
document.write(c1() + "<br/>");
document.write(c1() + "<br/>");
document.write(c1() + "<br/>");
document.write(c2() + "<br/>");
document.write(c2() + "<br/>");
//-->
</SCRIPT>
以上所有過程自動進行,代碼不需要任何設置(造成很多人不知道閉包原因)。
scope chain 簡單看來可以按照下面的代碼來描述:
函數體外Execution context 的scope chain? 只有 global.
function fun(){
?函數體內Execution context 的scope chain? fun的調用對象+global
??? function innerfun(){
????? inner函數體內Execution context 的scope chain innerfun的調用對象 + fun的調用對象 + global
??? }
}
但是ECMAScript提供的with表達式會修改scope chain.with表達式,我是能不用就不用了,<javascript權威指南>中也說with會造成性能的集聚下降。原文貼在下面。有時間再仔細研究。
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
?? 關于這部分我決定不按照原文直譯。Identifier Resolution是一個過程,而不是具體的概念,我舉個例子可能就明白了。
<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調用對象,global}
?pf('outer代碼開始');
?pf(s_global);//global
??? pf(s_outer);//outerfun調用對象
??? pf(s_inner);//global
?function innerfun(){////scope chain? {outerfun調用對象,global}
??? var s_inner='inner';//scope chain? {innerfun調用對象,outerfun調用對象,global}
?pf('inner代碼開始');
?pf(s_global);//global
??? pf(s_outer);//outerfun調用對象
??? pf(s_inner);//innerfun調用對象
?}
?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('第二個函數開始------------------------');
var b=outerfun();
b();
//-->
</SCRIPT>
其
實Identifier Resolution就是屬性查找的過程。 先從scope chain 的第一個對象開始找,如果找不到再從scope
chain的第二個對象找, global對象始終是scope chain 的最后一個對象,如果global
object中也找不到屬性,那為undefined.
有兩個注意點:
?? 如果可能,這個查找過程會對對象的prototype(原型對象)查找。先找實例屬性,再找原型屬性。見我的其它文章。
?? 在函數內,這個函數的調用對象包括的參數,local變量,inner函數等。
如果有對javascript語言感興趣的,歡迎交流批評。
http://www.tkk7.com/zkjbeyond/category/10156.html
參考:
??? 《javascript權威指南》
???? http://jibbering.com/faq/faq_notes/closures.html