以前對於閉包的理解只是很膚淺的,只是浮於表面,此次深究了一下閉包,下面是我對閉包的理解。javascript
引用高程裏的話 =>
閉包就是有權訪問另外一個做用域中變量的函數,閉包是由函數以及建立該函數的詞法環境組成而成
記住,閉包是一個函數,廢話很少說,先來個例子:html
function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc();
函數makeFunc裏面定義了一個函數,該函數引用了一個makeFunc內部的變量,而後返回該函數,myFunc變量承接了makeFunc返回的函數,也就是
function displayName() { alert(name); }
java
在下面執行該函數,咦!!!爲何函數裏面的name是從哪裏獲得的,爲何不報錯呢?
要搞明白其中的細節,必須從理解函數被調用的時候都會發生什麼入手node
當一個函數被調用的時候,會建立一個執行環境(執行上下文),以及相應的做用域,在函數執行完的時候該做用域鏈會被銷燬,函數裏面的變量也會被回收,可是閉包的狀況不是這樣的..
仍是上面的例子,打印了一下myFunc函數,發現該函數的做用域鏈(scopes)保存着name變量,name變量並無被回收,因而咱們成功的在makeFunc函數外的做用域取到了name變量,myFunc就是閉包,咱們重溫一下閉包的特色=>閉包是由函數以及建立該函數的詞法環境組成而成.
這下概念就理解的更清楚了吧!閉包
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <p id="help">Helpful notes will appear here</p> <p>E-mail: <input type="text" id="email" name="email"></p> <p>Name: <input type="text" id="name" name="name"></p> <p>Age: <input type="text" id="age" name="age"></p> <script type="text/javascript"> function showHelp(help) { document.getElementById('help').innerHTML = help; } var helpText = [ { 'id': 'email', 'help': 'Your e-mail address' }, { 'id': 'name', 'help': 'Your full name' }, { 'id': 'age', 'help': 'Your age (you must be over 16)' } ]; function returnHelp(i) { return helpText[i]; } function setupHelp() { for (var i = 0; i < helpText.length; i++) { var item = returnHelp(i); document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp(); </script> </body> </html>
原本想實現一個點擊輸入框,顯示提示信息的功能,可是結果是顯示的全是age信息。。。爲何???app
緣由是賦值給onfouse的是閉包,這些閉包是由他們的函數定義和在setupHelp做用域中捕獲的環境所組成的 這三個閉包在循環中被建立,但他們共享同一個詞法做用域,在這個做用域中存在一個變量item, 點擊輸入框的時候,item已經變成helpText 的最後一個了,因此。。。
怎麼解決呢?
既然聚焦輸入框後的回調函數裏面的item變量引用的是共享的,因此只要把他變成非共享的就能夠了,異步
function showHelp(help) { document.getElementById('help').innerHTML = help; } var helpText = [ { 'id': 'email', 'help': 'Your e-mail address' }, { 'id': 'name', 'help': 'Your full name' }, { 'id': 'age', 'help': 'Your age (you must be over 16)' } ]; function returnHelp(i) { return helpText[i]; } function setupHelp() { for (var i = 0; i < helpText.length; i++) { var item = returnHelp(i); //經過這種方法把每個item變成具體的實例,而不是變量,避免每一次聚焦的時候執行函數裏面的變量都是指向同一個數 document.getElementById(item.id).onfocus = change(item); } } function change(item) { return function() { showHelp(item.help); } } setupHelp();
這樣把每一次循環的item傳給外面的函數,而後外邊函數返回具體的實例而非變量,這樣就解決了問題。函數
下面再來看一個經典的例子 :code
<html> <body> <div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div> <script> var nodes = document.getElementsByTagName('div'); for(var i = 0, len = nodes.length; i < len; i++){ nodes[i].onclick = function(){ alert(i); } }; </script> </body> </html
若是你認爲點擊每個div會返回對應的索引,那麼恭喜你,你掉坑了!!!
沒錯,一開始我也掉坑了,可能你們會感到很疑惑,爲何點徹底是5,由於點擊事件是異步的,點擊的時候變量 i 已經變成5了,因此點擊的時候彈出的全是5,怎麼解決呢,閉包來了htm
for(var i = 0, len = nodes.length; i < len; i++){ (function(i){ nodes[i].onclick = function(){ console.log(i); } })(i) };
這段代碼是如何完美的解決問題的呢?
當咱們點擊的時候,回調函數裏面的 i 引用的是外面閉包的 i ,這樣問題就被完美的解決了。
在上面分析的過程當中也說到過,閉包不會被垃圾回收機制回收,會形成內存泄漏,記得閉包使用完手動把變量回收一下。