閉包對於初學者而言一直是一個不太好理解的概念。最近在學習javascript的時候碰巧看到了關於這方面的講解,本身才明白了許多,因此把它寫出來分享給你們。固然,本文也是參考了不少blog和書籍,加上本身的理解寫出來的,文章末尾會附上對應的參考文檔。javascript
//javascript的變量做用域能夠分爲兩種:全局變量和局部變量。 //在函數內聲明的變量就是局部變量,這個變量在函數體內可訪問,在函數外部沒法直接讀取局部變量。 //例如: var globalVariable = 1; //全局變量 function f() { var localVariable = 100; //局部變量 注意函數內的變量必定要加上關鍵字var才能成爲局部變量,否則就會成爲全局變量 } alert(globalVariable); //顯示1 alert(localVariable); //拋出錯誤 提示localVariable未聲明
//javascript內部函數又稱爲嵌套函數,就是在函數中聲明的函數 //函數能夠實現多級嵌套 //例如: function f() { function innerFunction() { //innerFunction是f的內部函數 function anotherInnerFunction() { //anotherInnerFunction是innerFunction的內部函數 } } }
//局部變量對於內部函數而言是可訪問的,整個做用域鏈只能向下傳遞。 //例如: function f() { var localVariable = 100; //局部變量 function innerFunction() { var anotherLocalVariable = 200; //局部變量 alert(localVariable);//內部函數能夠訪問外部函數的局部變量 } alert(anotherLocalVariable); //拋出錯誤 提示anotherLocalVariable未聲明 }
//javascript借鑑了函數式編程的概念,把函數提到了一等公民的地位,意思是函數能夠跟變量同樣被賦值,做爲傳入參數和返回參數。 //例如: function f1() { } var temp1 = f1; //函數能夠賦值給變量 function f2(f) { f(); } f2(f1); //函數f1能夠做爲函數f2的參數 function f3() { function f4() { } return f4; //函數f4能夠做爲函數f3執行後返回的參數 } var temp2 = f3(); //temp2的值是f4
若是單純從字面上來理解"閉包"這兩個字,每每讓人摸不着頭腦,因此咱們能夠更多地把"閉包"看成一個代號名稱,不要從字面上理解這個概念。參考了多處資料,發現閉包的官方定義很晦澀難懂,通過個人理解,以爲閉包的概念能夠這樣描述:當一個內部函數被調用,就會造成閉包。看代碼應該會更清晰一些:html
//在聲明它的函數中調用 function f() { var localVariable = 100; //局部變量 function innerFunction() { //訪問局部變量 localVariable += 1; alert(localVariable); } innerFunction(); } f(); //顯示101 //這樣子確實也造成了閉包,但暫時沒發現這樣作有什麼意義 //在聲明它的函數外部調用 function f() { var localVariable = 100; //局部變量 function innerFunction() { //訪問局部變量 localVariable += 1; alert(localVariable); } return innerFunction; //innerFunction做爲函數f的返回參數 } var temp = f(); temp(); //顯示101 temp(); //顯示102 //可見,閉包使得函數外部操做局部變量變爲可能 //假如咱們只是單純執行f(),不把結果賦值給全局變量temp,那麼執行完後,因爲不存在其餘引用,這時候函數f就會成爲垃圾回收的對象 //而因爲賦值給全局變量temp了,innerFunction引用到f函數,函數f的局部變量localVariable就不會被回收,因此就出現了上文中執行兩次temp(),實現累加效果
單純地知道閉包的概念是遠遠不夠的,要充分理解閉包這個概念,還得從它的使用場景入手。java
其實閉包的應用能夠簡單總結爲一句話:將變量維持在內存,帶來更好的安全性和封裝性。就像在上一節中使用的demo同樣,將localVariable維持在內存中,使得改變它變爲可能。其實,要實現這個功能,你可能以爲使用全局變量就能夠了,除非從新加載或關閉頁面,不然全局變量一直在內存中。可是使用全局變量其實不是一種很差的作法,最主要是由於全局變量是能夠手動更改的,例如定義一個globalVariable,你在接下來的代碼能夠爲所欲爲地修改,而使用閉包,想要修改變量,就只能經過調用已經實現的特定函數,學過面向對象的人都知道,這就是良好封裝性的體現,不能隨意修改,就保證了變量的安全性。例以下面的demo:node
function f() { var localVariable = 100; //局部變量 function innerPlus() { localVariable += 1; alert(localVariable); } function innerMinus() { localVariable -= 1; alert(localVariable); } return { plus: innerPlus, minus:innerMinus }; //此次返回的再也不是一個單獨的函數,而是一個對象,對象的屬性是兩個內部函數 } var temp = f(); temp.plus(); //顯示101 temp.minus(); //顯示100 //只能經過temp的特定API操做變量,實現了封裝性和更好的安全性
這裏再補充一個Demo,關於內部函數在聲明它的函數中調用的實際應用場景。編程
//對node節點的背景顏色作一次漸變 function fadeBgColor(node) { var level = 1; function fade() { if (level < 15) { var hex = level.toString(16);//將整型轉化爲16位的字符 如10對應a node.style.backgroundColor = '#FFFF' + hex + hex; level += 1;//訪問局部變量 setTimeout(fade, 200);//遞歸調用自身 } } setTimeout(fade, 200);//200ms後執行fade函數 } fadeBgColor(document.body);
接下來的這一小節將從閉包的微觀世界提及,展現一段javascipt代碼,經過對javascript解析器執行原理的剖析,逐步地探討閉包的內部機理。安全
function f() { var localVariable = 100; function innerFunction() { localVariable += 1; alert(localVariable); } return innerFunction; } var temp = f(); temp();
1. javascript解析器會在頁面加載後建立一個全局執行上下文(Execution Context),全局執行上下文的做用域鏈中只有一個全局對象,它定義了javascript中全部可用的全局變量和函數,全局對象在初始化的時候會包含this,window,document等對象,全局對象會隨着javascript的執行動態地新增對象。網絡
2. 當javascript解析器執行到函數聲明function f(){...}的時候,由於函數f是一個全局函數,因此會把全局執行上下文的做用域鏈賦給函數f的內部屬性scope(內部屬性沒法經過javascript直接訪問)。閉包
3. 接下來var temp = f(),這時javascript解析器會執行函數f,同時建立一個對應的執行上下文,並建立一個活動對象,並初始化給this和函數傳入參數arguments,同時加入函數f中全部的變量(初始化爲undifined),隨着函數的執行變量將逐步被賦值。活動對象會出如今執行上下文做用域鏈的頂端,緊接着是f函數的scope屬性中的對象。ide
4. 執行f函數的時候,javascript解析器會完成函數innerFunction的聲明,這時innerFunction的執行上下文是函數f的執行上下文,因此會把函數f的執行上下文做用域鏈賦值給innerFunction的scope屬性。函數式編程
5. 最後執行temp(),其實就是執行innerFunction(),這時候會建立一個對應的新的執行上下文,同時建立temp函數的活動對象。一樣活動對象會出如今做用域鏈的頂端,接下來是scope屬性的做用域鏈。
此時,函數f從聲明到執行的步驟就完成了。最終函數temp的做用域鏈引用着函數f的活動對象,因此在函數f返回後就不會被GC回收。從這裏也能夠清晰地看到,函數temp在查找localVariable變量的時候,首先先從自身的活動對象中查找,找不到再到原型對象(有關原型對象這裏就暫時不討論了)中找,以後就到函數f的活動對象中找,再找不到就到全局對象中查找。
若是你有什麼新的想法,歡迎留言討論。
參考文獻: 阮一峯的網絡日誌-學習javascript閉包,javascript權威指南第8章閉包,高性能網站建設進階指南第7章編寫高效的javascript,閉包維基百科,javascirpt語言精髓4.8節
修改記錄: [2014.8.25 第一次修改 主要修改了閉包的概念] [2014.9.23 第二次修改 主要修改了活動對象的描述] [2014.11.28 第三次修改 新增內部函數在聲明它的函數中調用的案例]