開始正式介紹之前先看一個比較有難度的閉包的面試題:
function fun(n,o) {
fun :function(m){
return fun(m,n);
}
var a = fun(0); a.fun(1); a. fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);## . (1); c.fun(2); c.fun(3);
//問:三行a,b,c的產出分別為何?
(原文連結:http://www.cnblogs.com/xxcanghai/p/4991870.html)
這題是個人拿到手都會感到棘手,尤其對某些基礎不紮實的新手,看著看著就看暈了,我大體解釋一些這個fun函
乾了一些什麼事(文章最後再分析一下這個): 首先,印了一下他的第二個參數,然後return了一個對象,這個對像有一個方法fun,然後這個方法re
turn了fun(m,n)--最外層函數的執行結果;大體弄懂基本的流程,就可以大概分析一下了,不過在在講閉包之前,還是先講一下
js的作用域鏈:
每一段js程式碼(全域程式碼或函數)(注意一下這裡,敲黑板!!!)都有一個與之關聯的作用域鏈(scope chain)。這
個作用域鍊是一個物件列表或鍊錶,這組物件這組物件定義了這段程式碼作用域中的變量,當js需要找出變數x的值得時候,
(這個過程叫做'變數解析(varaible resolution)'),他會從鏈中的第一個物件查找,如果在第一個物件中找不到就會
查找下一個,以此類推。
在js頂層程式碼中,作用域鏈由一個全域物件組成。在不包含巢狀的函數體內,作用域鏈上有兩個對象第一個是該函數
定義參數和局部變數的對象,第二個是全域對象。
--以上兩段的內容來自js權威指南,就是那本犀牛書
當定義一個函數時,它實際上保存一個作用域鏈。當呼叫這個函數時,它會建立一個新的物件來儲存他的局部變量,並將
這個物件加入到保存的那個作用域鏈上,同時創建一個新的更長的表示函數呼叫作用域的鏈。對於巢狀函數來講每次呼叫
外部函數時,作用域鏈都是不同的,內部函數又會重新定義一遍,每次呼叫外部函數時,內部函數的程式碼都是相同的,而
關聯這段程式碼的作用域鏈也不相同。
--以上內容也是來自js權威指南,就是那本犀牛書
大概概括一下就是每個函數會形成一條作用域鏈,作用域鏈不同,能訪問到的內容也不盡相同,由於作用域鏈上物件的
排序關係存取變數時,可以遵循一個原則--就是就近原則。
說完作用域之後,就來談談閉包吧。
簡言之,混亂的作用域鏈就是形成閉包的元兇。
來一個極簡單的例子:
var d;
function outter(){
(c);
d = function() { c++; }
}
outter();
d();//0
d();//1
d(#);//1
d(#);一般而言,每次呼叫js函數時,都會建立一個新的物件用來保存局部變量,並將這個物件加入到作
用域鏈中,當函數傳回之後,就從作用域鏈中將這個物件刪除,如果不存在巢狀函數也沒有其他引用指向這個綁定物件,他就
會被當作垃圾回收。如果定義了巢狀函數,每個巢狀函數都會形成自己的作用域鏈,而這個作用域鏈指向一個變數綁定對
象,如果這些巢狀的函數物件在外部函數中保存下來,那麼他們也會和所指向的變數綁定物件一樣被當作垃圾回收。但是如
果實將這些巢狀函數作為返回值返回,或儲存在某處的屬性裡,就會有一個外部的引用指向這個嵌套的函數。它就不會被當做
垃圾回收,並且所指向的變數綁定對象,也不會被當作垃圾回收!
所以在上邊的程式碼裡當呼叫outter之後,由於外部變數d引用了巢狀的函數,故而在outter執行完畢之後,d指向的巢狀
函數何其所指向的變數綁定物件是沒有被回收的,所以c也沒有回收,所以有了往後的故事。 。 。
來一個閉包的英文解釋(以下摘自MDN的解釋):
A closure is the combination of a function andthat#Vlex) clared.
什麼意思?上邊話直接翻譯過來就是:閉包是函數和這個函數被宣告的詞法作用域環境的組合。
再說開頭說到的這題:
function fun(n,o) {
fun:function(m) {
return fun(m,n);
}
var b = fun(0).fun(1).fun(2).fun (3);
我來解釋這語句執行時會發生的情況:
fun(0):
fun(0) => n = 0,o = undefined;
ined;
console.log(o); => o = undefined;列印undefined
return { fun: function(m){ return fun(m,n )使用) function(m){ return fun(m,n )使用中定義嵌套};並由return的物件的屬性
fun引用,m = undefined,n =0 ,這條作用域鏈保留
fun(0).fun(1):
實際上是呼叫返回物件的fun方法:
function(m) => m = 1;
return fun(m,n)##o # fun(n,o) => n=1,o=0;
console.log(o) =. => m = undefined,n =1; 同上,作用域鏈被保存
fun(0).fun(1).fun(2):
# function(m) => m=2
return fun(m,n) m=2,n=1
o ## 然後再印o就是印1啦。 。 。
接著
fun(0).fun(1).fun(2).fun(3);
function(m) => m =3
return fun(m,n) m=3,n=2
然後再印o就是印2啦。 。 。
...
最後的b是呼叫了返回對象的fun方法,而fun執行的時候又回傳了一個對象,所以b是一個對象,裡面有一個鍵為fun值函數的
屬性,這種屬性我們一般叫他方法。
其實最後才是我要講的重點,如果為了炫技或條件必須得情況下,使用閉包外,閉包能避免就避免,因為只要你使用了
閉包就意味著你在記憶體裡劃出了一塊地方,你的程式可能不一定會用,而電腦在運行時其他的程式卻一點不能利用的空間,
對程式的效能無疑有影響。
以上是JS閉包隨筆的詳細內容。更多資訊請關注PHP中文網其他相關文章!