先解釋一下什么是“自由變量”
在A作用域中使用的變量x,卻沒有在A作用域中聲明(在其他作用域中聲明),對于A作用域來說,x就是一個自由變量
1 var x = 10;
2 function fn() {
3 var b = 20;
4 console.log(x+b); // 這里x就是一個自由變量
5 }
如上程序中,在調用fn()
函數時,函數體中第4行。取b的值就可以直接在 fn 作用域中取,因為 b 就是在這定義的。但 x 的值,需要在另一個作用域中取,那么到哪個作用域中取呢?
有些說法是到父作用域中取,其實有時候這個說法會產生歧義,如
1 var x = 10
2 function fn() {
3 console.log(x)
4 }
5 function show(f) {
6 var x = 20;
7
8 (function () {
9 f(); // 10, 而不是20
10 })();
11 }
12 show(fn);
所以用上面的說法,不太貼切,這句話更為貼切 ------要到創建這個函數的那個作用域中取值---是“創建”而不是“調用”,切記切記 ----其實這就是所謂的靜態作用域
對于本文第一段代碼,在fn函數中,取自由變量x的值時,要到哪個作用域中取?——要到創建fn函數的那個作用域中取——無論fn函數將在哪里調用。
上面描述的只是跨一步作用域去尋找。
如果跨了一步,還沒找到呢?——接著跨!——一直跨到全局作用域為止。要是在全局作用域中都沒有找到,那就是真的沒有了。
這個一步一步“跨”的路線,我們稱之為——作用域鏈。
我們拿文字總結一下取自由變量時的這個“作用域鏈”過程:(假設a是自由量)
第一步,現在當前作用域查找a,如果有則獲取并結束。如果沒有則繼續;
第二步,如果當前作用域是全局作用域,則證明a未定義,結束;否則繼續;
第三步,(不是全局作用域,那就是函數作用域)將創建該函數的作用域作為當前作用域;
第四步,跳轉到第一步。
以上代碼中:第13行,fn()返回的是bar函數,賦值給x。執行x(),即執行bar函數代碼。取b的值時,直接在fn作用域取出。取a的值時,試圖在fn作用域取,但是取不到,只能轉向創建fn的那個作用域中去查找,結果找到了。
“閉包”概念不太好解釋
只需記住兩種情況就行了----==函數作為返回值,函數作為參數傳遞==
1 函數作為返回值
function fn() {
var max = 10;
return function bar(x) {
if ( x > max ){
console.log(x);
}
}
}
var f1 = fn()
f1(15) // 15
如上代碼,bar函數作為返回值,賦值給f1變量。執行f1(15)時,用到了fn作用域下的max變量的值。至于如何跨作用域取值,可以參考上面。
2 函數作為參數被傳遞
var max = 10,
fn = function (x) {
if ( x > max ){
console.log(x);
}
};
( function (f) {
var max = 100;
f(15);
})( fn );
// 15
如上代碼中,fn函數作為一個參數被傳遞進入另一個函數,賦值給f參數。執行f(15)時,max變量的取值是10,而不是100。
上面講到自由變量跨作用域取值時,曾經強調過:==要去創建這個函數的作用域取值,而不是“父作用域”==。理解了這一點,以上兩端代碼中,自由變量如何取值應該比較簡單。(不明白的朋友一定要去上面看看,這個很重要!)
另外,講到閉包,除了結合著作用域之外,還需要結合著執行上下文棧來說一下。
執行上下文棧可以看這篇(http://www.cnblogs.com/wangfupeng1988/p/3989357.html)
其實調用函數會進行上下文環境的壓棧,當一個函數被調用完成之后,其執行上下文環境將被銷毀,其中的變量也會被同時銷毀。
但是在當時那篇文章中留了一個問號——有些情況下,函數調用完成之后,其執行上下文環境不會接著被銷毀。這就是需要理解閉包的核心內容。
稍微分析一下
第一步,代碼執行前生成全局上下文環境,并在執行時對其中變量進行賦值。此時全局上下文環境是活動狀態
第二步,執行第17行代碼時,調用fn(),產生fn()執行上下文環境,壓棧,并設置為活動狀態。
第三步,執行完17行,fn()調用完成。按理說應該銷毀掉 fn() 的執行上下文環境,但是這里不能這么做。注意重點來了:因為執行 fn() 時,返回的是一個函數。函數特別之處是可以創建一個獨立的作用域,而返回的這個函數體中,有一個自由變量 max 要引用 fn 作用域下的 fn() 上下文環境中的max。因此這個 max 不能被銷毀,銷毀之后 bar 函數中的 max 就找不到值了
因此,這里的 fn() 上下文環境不能被銷毀,還依然存在于執行上下文棧中。
-- 即,執行到
第18行時,全局上下文環境將變為活動狀態,但是fn()上下文環境依然會在執行上下文棧中。另外,執行完第18行,全局上下文環境中的max被賦值為100。如下圖:
第四步,執行到第20行,執行f1(15),即執行bar(15),創建bar(15)上下文環境,并將其設置為活動狀態。
執行bar(15)時,max是自由變量,需要向創建bar函數的作用域中查找,找到了max的值為10。
這里的重點就在于,創建bar函數是在執行fn()時創建的。==fn()早就執行結束了,但是fn()執行上下文環境還存在與棧中,因此bar(15)時,max可以查找到。如果fn()上下文環境銷毀了,那么max就找不到了==
使用閉包會增加內容開銷,現在很明顯了吧!
第五步,執行完20行就是上下文環境的銷毀過程,這里就不再贅述了。