簡單的說閉包就是函數裏面的函數,《JavaScript高級程序設計》裏是這樣定義的javascript
閉包是指有權訪問另外一個函數做用域中的變量的函數。html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>lzhTest</title> </head> <body> <ul> <li>0</li> <li>1</li> </ul> <script> var lis = document.getElementsByTagName("li"); for(var i = 0; i < lis.length; i++){ lis[i].onclick = function(event){ alert(i); } } </script> </body> </html>
分別點擊 li,alert什麼?答案均是 2. 爲何呢?咱們接着往下看前端
函數被調用時會建立一個執行環境和做用域鏈 (scope chain),做用域鏈中每一個元素都指向一個活動對象或變量對象 (執行環境中定義的全部變量和函數都保存在這個對象中,包括 this、arguments),函數執行完畢,做用域鏈被銷燬,若是這時相應的變量對象沒有被引用,則變量對象佔用的空間會被釋放。
好比上面題目中的做用域鏈是這樣的:
java
匿名函數1
和 匿名函數2
是兩個事件處理函數,從圖中能夠看出,在做用域鏈的最前端(即下標爲0)對應的活動對象中,是不存在 i
的,i
在全局變量對象中,點擊的時候,須要往做用域的上層查找 i
,因而就找到了全局變量對象中的 i
,由於點擊的時候,i
早已增長成爲 2,因此 alert 的 i
均爲 2。jquery
直接看代碼:面試
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>lzhTest</title> </head> <body> <ul> <li>0</li> <li>1</li> </ul> <script> var lis = document.getElementsByTagName("li"); var helper = function(i){ return function(event) { alert(i); } } for(var i = 0; i < lis.length; i++){ lis[i].onclick = helper(i); } // 參考自:《JavaScript語言精粹》 </script> </body> </html>
對應的做用域鏈及活動對象:
瀏覽器
匿名函數1
,(根據《JavaScript高級程序設計》介紹:函數在調用時生成做用域鏈和活動對象,但閉包被返回時,就會生成做用域鏈和活動對象(中文第3版 P180 line7),個人理解並非這樣的),返回的 匿名函數1
引用着 helper1 中的i,helper(1) 執行完畢,以後 helper(1)的執行環境和做用域鏈銷燬,可是 helper(1) 的活動對象還在,由於 匿名函數1
的做用域鏈還在引用着它(按照個人理解應該是 匿名函數1
還引用着它)。(執行環境和做用域鏈銷燬這一過程在圖中沒有體現出來)。匿名函數1
,(按照個人理解:此時匿名函數1的執行環境纔會被壓入環境棧中,同時生成 匿名函數1
的做用域鏈和活動對象),alert(i)
時,由於 匿名函數1
的活動對象中找不到 i
因此往做用域鏈的父級找,找到了 helper(1) 活動對象中的 i,因而 alert
了 0alert 1
,若是此時還不是很懂的話,能夠回頭再看看圖,或者從 代碼1 那裏重新理解。若是有認真看的話,請思考一下個人見解和《JavaScript高級程序設計》的見解,到底哪一個是正確的,我仍是堅持本身的見解,若是有錯的話,還請指出。閉包
因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多。模塊化
來看一個閉包內引用着活動對象中的變量時,活動對象不被釋放的例子,注意看圖片右側的 scope:
函數
從圖中能夠看出,param一、param3 所在的活動對象不在做用域鏈中,應該是被通知準備回收了或者已經回收了,而 param二、param4 所在的變量對象還在。
另外,若是閉包中有 eval
的話,因爲不能判斷 eval
裏是否有引用父級做用域鏈活動對象中的變量,那麼該做用域鏈中的全部活動對象都會被保留,因此在閉包中儘可能不要使用 eval
:
但若是在裏面用的是 new Function("這裏引用閉包外的變量")
這種寫法,若是沒有其它引用,父級做用域鏈的活動對象是不會保留的,下面這種寫法最終會報錯 Uncaught ReferenceError: param1 is not defined
:
若是閉包的做用域鏈中保存着一個HTML 元素,那麼就意味着該元素將沒法被銷燬,代碼以下,只要匿名函數存在,element 的引用數至少也是 1,所以它所佔用的內存就永遠不會被回收。(書中有討論到這是IE9之前的問題,我怎麼以爲這是個廣泛的問題呢,難道 Chrome 或其它瀏覽器中 element 的引用數至少還能是0嗎?求不吝賜教)。
function assignHandler(){ var element = document.getElementById("someElement"); element.onclick = function(){ alert(element.id); }; }
function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ alert(id); }; element = null; }
閉包無處不在,若是真的要概括幾個用處,以下:
咱們都知道,在 JavaScript 中,是不存在塊級做用域的,也就是在 {} 裏面聲明的變量,在 {} 外面依然能夠訪問。但若是利用函數一旦執行完,其中執行環境和做用域鏈均銷燬的特性,咱們能夠這麼作:
(function(){ // 讓一個括號包着一個函數,至關於獲得這個函數的引用, // 而後再在後面加個括號,執行這個函數,稱這種函數爲 當即執行函數 // 在這裏面聲明的變量,外部不可訪問,除非 return 一個閉包或變量 // 若是此時外部是一個函數的話,那這個當即執行函數也是一個閉包 // 只是這個閉包並無返回些什麼 })();
有了上面提到的模仿塊級做用域,就能夠減小全局變量的使用,jQuery 就是這麼作的:
(function(window, undefined){ // ... // 這裏實現 jQuery 的全部功能 // 勢必會聲明不少變量,若是暴露在全局做用域中會形成命名衝突 // 因此用一個當即執行函數包起來,可是爲何要傳入 window 呢 // 傳入 window 是爲了讓 window 成爲當前做用域下的變量,這樣能夠減小訪問成本, // 另外便於壓縮,好比將 window 壓縮成 e,外部傳進來的依然是 window // 傳入 undefined 的緣由: // 在低版本的 IE 中,undefined 是可寫的,有可能 undefined 就不是 undefined 了 window.jQuery = window.$ = jQuery; // 這裏的 jQuery 就是一個函數,平時咱們用的時候是 $(參數、參數),因此,他也是個閉包嘛 // 上面是模塊中的一種,而旦還有更高級的,jQuery 兼容 AMD 規範 if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define( "jquery", [], function () { // AMD 這裏不詳細介紹了,這裏就是 define 一個模塊 // 這裏也是一個閉包 return jQuery; // 這裏就是閉包中的閉包了 } ); } }(window))