一、閉包(Closure)
1.1、什么是閉包?
理解閉包概念:
a、閉包是指有權限訪問另一個函數作用域的變量的函數,創建閉包的常見方式就是在一個函數內部創建另一個函數,也就是創建一個內部函數,創建一個閉包環境,讓返回的這個內部函數保存要引用的變量,以便在后續執行時可以保持對這個變量的引用。
b、只要存在調用內部函數的可能,JavaScript就需要保留被引用的函數。而且JavaScript運行時需要跟蹤引用這個內部函數的所有變量,直到最后一個變量廢棄,JavaScript的垃圾收集器才能釋放相應的內存空間。
c、Javascript語言特有的"鏈式作用域"結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。
d、如果一個內部函數被調用且引用了它的外部變量那么它就是一個閉包。
相信你看了上面的這段話可能還不理解什么是閉包,那么我就舉一個閉包的經典例子來幫助你理解閉包的概念吧。
請看下面這段代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>閉包</title> </head> <body> <script type="text/javascript"> function out() { var i = 0; function inner() { alert(++i); } return inner; } var ref= out(); ref(); </script> </body> </html>
結果:
上面的代碼有兩個特點:
1、創建兩個函數out,inner,函數inner嵌套在函數out內部,也可以說是函數inner是函數out的內部函數;
2、調用函數out返回內部函數inner。
這樣在執行完var ref=out()后,變量ref實際上是指向了函數inner,也可以說是引用了函數inner,再執行ref()后就會看到上圖彈出一個窗口顯示i的值第一次為1。
這段代碼其實就創建了一個閉包,為什么?因為函數out外的變量ref引用了函數out內部的函數inner,也就是說:
當函數out的內部函數inner被函數out外的一個變量ref引用的時候,就創建了一個閉包。
可能你還是不理解閉包這個概念,因為你不知道閉包有什么用,那么先理解一下閉包的作用吧。
1.2、為什么要用閉包(作用)?
1.2.1、保護函數內的變量安全。
解釋:以上面的的例子為例,函數out中i只有函數innder才能訪問,而無法通過其他途徑訪問到,因此保護了i的安全性。
1.2.2、通過訪問外部變量,閉包可以保存這些變量的上下文環境
在上面的示例中增加了一次函數ref()的調用,執行的結果如下:
解釋:當函數out執行完並且最終退出時,它的局部變量會被Javascript的垃圾回收機制回收所占用的資源,也就是局部變量被銷毀,但是因為創建了閉包環境,那么內部函數inner就會暫時保存外部函數的局部變量上下文環境。不會被垃圾回收機制回收。所以函數out中的i被復制一份暫時保存下來,這樣每次執行ref(),i都是自加1后alert輸出i的值。這只是我對閉包作用的簡單初淺理解,不專業也不嚴謹,但大概意思就是這樣,理解閉包需要循序漸進的過程。
相信你看了閉包的作用,對理解什么是閉包是否更明白一些,如果還是很疑惑,那么我就再舉幾個閉包的經典例子來幫助你理解閉包的概念吧。
1.3、閉包的經典示例
1.3.1、示例一
問題:假如我們有如下需求請在頁面中放10個div,每個div寫上對應的數字,當點擊每一個div時顯示索引號,如第1個div顯示0,第10個div顯示9。
可能你會這樣做:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>閉包</title> <style type="text/css"> div { width: 50px; height: 50px; background: lightcoral; float: left; margin: 20px; font: 15px/50px "microsoft yahei"; text-align: center; } </style> </head> <body> <div>div-1</div> <div>div-2</div> <div>div-3</div> <div>div-4</div> <div>div-5</div> <div>div-6</div> <div>div-7</div> <div>div-8</div> <div>dvi-9</div> <div>div-10</div> <script type="text/javascript"> var divs=document.getElementsByTagName("div"); for (var i=0;i<divs.length;i++) { divs[i].onclick=function(){ alert(i); } } </script> </body> </html>
結果:
從上面的結果你會發現,不管你點擊了哪個div,彈出的框div索引總是10,這可能會讓你很意外,會產生疑惑,為什么會出現這樣的結果呢?
解釋:因為點擊事件的函數內部使用外部的變量i一直在變化,當我們指定click事件時並沒有保存i的副本,這樣做也是為了提高性能,但達不到我們的目的,我們要讓他執行的上下文保存i的副本,這種機制就是閉包。
使用閉包可以解決此問題,代碼做了如下修改:
<script type="text/javascript"> var div=document.getElementsByTagName("div"); for (var i = 0; i < div.length; i++) { div[i].onclick=function(n){
return function(){ alert(n);//產生閉包,引用外部變量。 } }(i); } </script>
結果:
從上面的結果你會發現,使用閉包后,就達到你預期的結果了。
解釋:n是外部函數的值,但是內部函數需要使用這個值,因為產生閉包執行環境,所以返回函數前的n被臨時駐留在內存中給點擊事件使用,簡單說就是函數的執行上下文被保存起來,i生成了多個副本。
1.3.2、示例二
示例代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> function out() { var i = 10; return function inner() { i++; alert(i);//引用了外部變量,創建了閉包環境 }; } //此處為函數調用,第一個括符為調用out方法,第二個括符為調用返回的inner方法。 out()(); </script> </body> </html>
運行 結果:
1.3.3、示例三
示例代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>閉包</title> </head> <body> <script type="text/javascript"> var name = "this is Window"; var obj = { name: "My Object", getNameFunc: function() { alert(this.name);//輸出My Object return function() { return this.name; }; } }; var funn=obj.getNameFunc(); alert(funn());//輸出this is Window </script> </body> </html>
運行結果:
解釋:funn是屬於方法調用,所以this綁定到了obj對象,自然this.name就是"My Object",但是閉包函數無法訪問這個this,它只能訪問到全局的this,所以this.name就是“this is Window”.
總結:相信通過以上是幾個閉包示例,你對閉包也有一定的理解了吧。限於本人才疏學淺,對閉包的理解也並不是很透徹,只是理解了一些表面,會使用而已。若你想理解的更深入推薦你去看stackoverflow這個網站上對閉包的解釋:http://stackoverflow.com/questions/111102/how-do-javascript-closures-work