終於講到閉包了,當你在百度上搜索閉包時,你會被搜索出來的結果嚇一跳,我的天,爲什麼說得都不一樣?直到把所有的解釋都看過了,我就只想說一句,到底誰說的是對的…
在這麼多的不同解釋裏,我認真思考了很久,到底該相信誰?最後我選擇相信大道至簡,因爲我始終覺得理論來源於實踐,而實踐一定不是在象牙塔裏,而是可以摸得到的簡單的東西。
下面就來講最原始的閉包的示例:
function fn(){
var max=10;
return function bar(x){
if(x>max)
{
alert(x);
}
}
}
var f1=fn();
f1(15);//15
如上代碼,bar函數作爲返回值,賦值給f1變量。執行f1(15)時,就相當於執行bar(15),沿着作用域鏈取到fn作用域下的max變量的值。
這已經產生了一個最基本的閉包,用自然語言描述的話就是:
(1)定義普通函數 A
(2)在 A 中定義普通函數 B
(3)在 A 中返回 B
(4)執行 A, 並把 A 的返回結果賦值給變量 C
(5)執行 C
而它的形式就是用“return”作爲橋樑,鏈接A外的變量C和A內的變量B。
用一個歸納的話說就是:
當一個內部函數被其外部函數之外的變量引用時,就形成了一個閉包
如果你認爲這就是閉包的全部,那你就有些狹隘了。
下面一個例子,就展示了這個狹隘之處:
function A(){
var count=0;
function B(){
count++;
alert(count);
}
return B;
}
var c=A();
c();//1
c();//2
c();//3
那麼你就會驚奇於閉包還有如此神祕的一面,其實對於這一面,網上有很多解釋,解釋得五花八門,但大都沒解釋到本質上。
其實這一系列的文章的標題都是“javascript執行上下文、作用域與閉包”,說明閉包與執行上下文,作用域是有緊密聯繫的,下面就來演示一下閉包那神祕的一面的本質。
咱們可以拿本文的第一段代碼(稍作修改)來分析一下。
function fn(){
var max=10;
return function bar(x){
if(x>max)
{
alert(x);
}
}
}
var f1=fn(),
max=100;
f1(15);//15
我們來一步一步揭開那神祕的面紗:
在這之前,告訴大家一個很重要的東西—-我們在前面講執行上下文棧時當一個函數被調用完成之後,其執行上下文將被被彈出,即銷燬,其中的變量也會被同時銷燬。
但我們要銘記:在閉包裏,函數調用完成之後,其執行上下文環境不會立即被銷燬。(這句話是不正確的,但這裏讀者可以先用這個錯誤的解釋去看下面的步驟,先對閉包形成的原因有一個大致的瞭解,最終正確的理解在 厚積薄發—從此再也不用擔心閉包問題)
第一步,代碼執行前生成全局上下文環境,並在執行時對其中的變量進行賦值。此時全局上下文環境是活動狀態:
第二步,執行第17行代碼時,調用fn(),產生fn()執行上下文環境,壓棧,並設置爲活動狀態。
第三步,執行完第17行,fn()調用完成。按理說應該銷燬掉fn()的執行上下文環境,但是因爲執行fn()時,返回的是一個函數。函數的特別之處在於可以創建一個獨立的執行上下文,當代碼執行到return時,按理說應該把fn()的執行上下文銷燬,但是由於return的bar()存在對fn()上下文的引用,(因爲存在對變量max的需要)所以不能將其銷燬,這裏就要提到js的垃圾回收機制,當一個函數不存在外部對它的引用的時候,就自動將其銷燬。這裏存在對把不能把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就找不到了。
可以看到,使用閉包會使變量保存在內存中,但是缺點就是會增加內存開銷。
在網上有些資料解釋閉包的主要用途有兩個,一個是可以使外部變量訪問到一個函數的內部變量,一個是使變量保存在內存中。其實這句話不太貼切,正確的說法應該是:使外部變量訪問到一個函數的內部變量是閉包的形式,使變量保存在內存中是閉包的工作原理。
到這裏,如果覺得有種恍然大悟的感覺,不妨試一試下面的例子
function A(){
var count=0;
function B(){
count++;
alert(count);
}
return B;
}
var c=A();
c();//1
c();//2
c();//3
大家可以試一試解釋爲什麼三次調用c(),結果分別是1,2,3呢?
如果還有些不懂,我會在下一篇裏詳細講一下我的理解。
本文參考了王福朋老師的深入理解javascript原型和閉包(15)——閉包