1、Introduction javascript
Closure (閉包)html
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). java
閉包是ECMAScript(javascript)語言強大的特徵之一,若是沒有真正的理解它的概念,不可能很好使用它。在通常瀏覽器環境中,它們很容易被創建,但也會形成比較難理解的代碼邏輯。爲了不閉包引發的缺點,利用它所提供的優勢,明白它的機制是重要的。javascript語言的閉包很大程度上依靠 scope chains(函數,變量的範圍鏈) 和 javascript對象的靈活的屬性機制 實現。node
閉包簡單的解釋是,ECMAScript容許inner functions(嵌套函數):函數能夠定義在另一個函數裏面(關於嵌套函數能夠看看
我估計如下代碼就是一個閉包。算法
< script > express
var g_count = 0 ;數組
function aaa(p) { // outer function 瀏覽器
var outer_count = 0 ;閉包
function innerfun(name,count) { // outer function中定義的另一個inner function
return name + ' : ' + count + ' 次; ' ;
}
return function() { // 返回一個匿名的inner函數
var inner_count = 0 ;
return innerfun( ' g_count ' ,( ++ g_count)) + innerfun( ' outer_count ' ,( ++ outer_count))+innerfun('inner_count',(++inner_count))+p;
}
}
var fun1=aaa("fun1");
var fun2=aaa("fun2");
alert(fun1)
alert(fun1());//這時候才真正執行
alert(fun2());
不幸的,徹底明白閉包須要知道它背後的機制和一些技術細節。
2、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的使用,我這就不譯了。
能夠參見個人另外一篇文章
3、Identifier Resolution, Execution Contexts and Scope Chains
一、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.
二、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 回覆內的代碼
以上全部過程自動進行,代碼不須要任何設置(形成不少人不知道閉包緣由)。
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表達式,我是能不用就不用了,
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.
三、Identifier Resolution
關於這部分我決定不按照原文直譯。Identifier Resolution是一個過程,而不是具體的概念,我舉個例子可能就明白了。
其實Identifier Resolution就是屬性查找的過程。 先從scope chain 的第一個對象開始找,若是找不到再從scope chain的第二個對象找, global對象始終是scope chain 的最後一個對象,若是global object中也找不到屬性,那爲undefined.
有兩個注意點:
若是可能,這個查找過程會對對象的prototype(原型對象)查找。先找實例屬性,再找原型屬性。見個人其它文章。
在函數內,這個函數的調用對象包括的參數,local變量,inner函數等。
若是有對javascript語言感興趣的,歡迎交流批評。
http://www.blogjava.net/zkjbeyond/category/10156.html
參考:
《javascript權威指南》
http://jibbering.com/faq/faq_notes/closures.html
書接上回,繼續閉包。
Closures
一、自動的垃圾回收
ECMAScript有自動的垃圾回收機制。與java相似。可是規範也沒有對該機制詳細定義,而是讓瀏覽器等規範實現廠家來實現,各類瀏覽器實現不同,垃圾回收的算法也不一樣。好象ie的實現會出現內存溢出問題。對我咱們來講注意到這點就夠了,後面會提到如何避免ie的這個bug.
關於上篇提到的execution context,調用對象,參數,scope chain 等等都須要內存,垃圾回收機制會在適當時候釋放內存。
二、閉包如何造成
通俗的說,當一個(outer)函數的返回類型是(inner)函數類型時,這個被返回的inner函數斥又outer函數的scope chain,這時候閉包造成。 以下例:
function exampleClosureForm(arg1, arg2){
var localVar = 8;
function exampleReturned(innerArg){
return ((arg1 + arg2)/(innerArg + localVar));
}
/* return a reference to the inner function defined as -
exampleReturned -:-
*/
return exampleReturned;
}
var globalVar = exampleClosureForm(2, 4);
如今exampleClosureForm(2, 4)返回的inner函數不能被垃圾回收,由於它被變量globalVar持有,並可執行globalVar(n)。
可是內部的原理沒有表面上那麼簡單。如今globalVar是個函數對象,它的[[scope]] property 指向一個scope chain,而這個scope chain 包括 exampleClosureForm函數的調用對象+global對象。因此垃圾回收不能回收這部份內存。
一個閉包造成了。inner函數對象有本身的變量,也能夠訪問exampleClosureForm函數調用過程當中的參數,local變量等。
在上面的例子中,globalVar(n)執行時,在經過調用對象能夠訪問到exampleClosureForm(2, 4)執行過程當中的參數,local變量等。arg1 = 2,arg2 = 4 ,localVar=8,這些屬性都經過調用對象"ActOuter1"能夠獲得。
若是增長如下代碼,又返回另一個inner 函數。
var secondGlobalVar = exampleClosureForm(12, 3);
exampleClosureForm(12, 3)會引發新的調用對象建立,咱們定義爲ActOuter2。這個過程當中,arg1 = 12,arg2 = 3 ,localVar=8。第二個閉包造成了.
下面考慮返回的inner函數執行過程。如globalVar(2)。新的execution context、調用對象(ActInner)被建立。如今的scope chain是 ActInner1->ActOuter1->global object. 函數返回是 ((2 + 4)/(2 + 8)).
若是是secondGlobalVar(5)被執行狀況是什麼呢?如今的scope chain是ActInner2-> ActOuter2-> global object.函數返回是 ((12 + 3)/(5 + 8)).
經過比較,這兩個inner函數互不干擾的執行。若是嵌套更多的函數的話,與上面所訴相似。明白的javascript的閉包,從這個方面可能就能體會到它比java等慢n個數量級的緣由。
三、閉包能作什麼(例子)
(1)
function callLater(paramA, paramB, paramC){
return (function(){
paramA[paramB] = paramC;
});
}
var functRef = callLater(elStyle, "display", "none");
hideMenu=setTimeout(functRef, 500);
想象咱們作顏色漸變,或者動畫的時候吧。上面提供的函數多幽雅。
(2)
function associateObjWithEvent(obj, methodName){
return (function(e){
e = e||window.event;
return obj[methodName](e, this);
});
}
function DhtmlObject(elementId){
var el = getElementWithId(elementId);
if(el){
el.onclick = associateObjWithEvent(this, "doOnClick");
el.onmouseover = associateObjWithEvent(this, "doMouseOver");
el.onmouseout = associateObjWithEvent(this, "doMouseOut");
}
}
DhtmlObject.prototype.doOnClick = function(event, element){
... // doOnClick method body.
}
DhtmlObject.prototype.doMouseOver = function(event, element){
... // doMouseOver method body.
}
DhtmlObject.prototype.doMouseOut = function(event, element){
... // doMouseOut method body.
}
......
又一種註冊事件的方法。我以爲做者的這種實現可謂精妙。大大的開闊了個人思路。咱們能夠爲咱們的UI事件綁定到對象上,能夠很好的重用代碼。另外比起prototype.js的時間註冊來講簡單點。
(3)
var getImgInPositionedDivHtml = (function(){
var buffAr = [
'
'" style="position:absolute;top:',
'', //index 3, DIV top position
'px;left:',
'', //index 5, DIV left position
'px;width:',
'', //index 7, DIV width
'px;height:',
'', //index 9, DIV height
'px;overflow:hidden;\">
'', //index 11, IMG URL
'\" width=\"',
'', //index 13, IMG width
'\" height=\"',
'', //index 15, IMG height
'\" alt=\"',
'', //index 17, IMG alt text
'\"><\/div>'
];
return (function(url, id, width, height, top, left, altText){
buffAr[1] = id;
buffAr[3] = top;
buffAr[5] = left;
buffAr[13] = (buffAr[7] = width);
buffAr[15] = (buffAr[9] = height);
buffAr[11] = url;
buffAr[17] = altText;
return buffAr.join('');
}); //:End of inner function expression.
})();
這種匿名函數的調用在dojo中見過,如今再看,感受不同。
以上是原做者的例子,我抄過來的。下次我準備深刻研究一下閉包能給咱們開發js類庫提供什麼更好的思路。感受如今不少人對閉包瞭解很少,通過這段時間的思考,利用javascript中的閉包,代碼偶合性會更低。