JavaScript(8)— 閉包
- 2020 年 3 月 26 日
- 筆記
JavaScript(8)— 閉包
理解閉包
我的理解是:閉包就是能夠讀取其他函數內部變量的函數。由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以簡單這樣理解
"函數A中,有一個函數B,函數B中可以訪問函數A中定義的變量或者是數據,此時形成了閉包"。所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。
一、閉包的由來
1、函數嵌套函數
JS之所以會有閉包,是因為JS不同於其他規範的語言,JS允許一個函數中再嵌套子函數,正是因為這種允許函數嵌套,導致JS出現了所謂閉包。
function a(){ var a=1; function b(){ alert(a); }; b(); } a();
在JS正常的函數嵌套中,父函數a調用時,嵌套的子函數b的結構,在內存中產生,然後子函數又接着調用了,子函數b就註銷了,此時父函數a也就執行到尾,父函數a也會把
自己函數體內調用時生成的數據從內存都註銷。
function a(){ var a=1; function b(){ alert(a); } return b; } var f=a();
這個例子中,父函數調用時,函數體內創建了子函數b,但是子函數並沒有立即調用,而是返回了函數指針,以備「日後再調用」,因為「準備日後調用」,此時父函數a執行完了
也不敢註銷自己的作用域中的數據,因為一旦註銷了,子函數b日後再調用時,沿着函數作用域鏈往上訪問數據,就沒有數據可以訪問了,這就違背了JS函數作用域鏈的機制。
正因此,子函數要「日後調用」,導致父函數要維持函數作用域鏈,而不敢註銷自己的作用域,那麼這個子函數就是「閉包函數」。
2、JS變量的作用域
理解閉包還要理解一個知識點就是 JavaScript的變量作用域。與大多數語言相同,JavaScript變量的作用域分為全局變量和局部變量。**函數內部可以訪問全局變量,但是
函數外部無法訪問函數內部的局部變量**
示例
function f1(){ let n =100; var m=99; console.log(n); console.log(m); } f1(); //輸出:100 , 99 console.log(n); //輸出:undefined console.log(m); //輸出:undefined
注意
在函數內部聲明變量的時候一定要用let或者var。否則,實際上聲明了一個全局變量
思考
函數外部如何讀取局部變量?
要在函數外部讀取局部變量,可以通過在函數內部再定義一個函數的方法來實現。
示例
function f1(){ var n =100; function f2(){ console.log(n); } return f2; } let result =f1(); result(); //輸出100
在上面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2都是可見的。但是反過來就不行,f2內部的局部變量,對f1就是不可見的。這就是Javascript
語言特有的"鏈式作用域"結構,子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。
二、閉包的作用
閉包可以用在許多地方。它的最大用處有兩個 1、可以讀取函數內部的變量。2、讓變量的值始終保持在內存中。
第一個上面已經實現了,這裡就不再重複說明。
1、讓變量的值始終保持在內存中
一般來說,全局變量的生存周期是永久的,直到我們主動銷毀。而在函數內的局部變量來說,當退出函數時,這些函數變量立即失去它們的價值,也就被垃圾回收機制
銷毀了,也算壽終正寢。
示例
//普通的函數 function f1() { var num = 10; num++; return num; } console.log(f1()); //11 console.log(f1()); //11 console.log(f1()); //11
可是在閉包中,卻不是這樣。它可以讓這些變量的值使用保持在內存中(不被垃圾回收)
示例
//函數模式的閉包 function f2() { var num = 10; function f3() { num++; return num; } return f3; } var ff = f2(); console.log(ff());//11 console.log(ff());//12 console.log(ff());//13
由此可見,當退出函數後,局部變量 num 並沒有立即消失,一直存在,這樣在第二次調用時 num 才會是在 11的基礎上加1,是12,以後每次調用也才會不斷加1。
思考
為什麼會這樣呢?
原因就在於f2是f3的父函數,而f3被賦給了一個全局變量,這導致f3始終在內存中,而f3的存在依賴於f2,因此f2也始終在內存中,不會在調用結束後,被垃圾回收機制
(garbage collection)回收。
三、示例
為了更好理解閉包在實際開發中的應用,這裡舉幾個簡單例子來說明閉包。
1、產生三個隨機數,但是都是相同的
代碼
<script> //非閉包 function showRandom() { var num=parseInt(Math.random()*10+1); console.log(num); } showRandom(); //隨機 showRandom(); //隨機 showRandom(); //隨機 console.log("==========================="); //閉包的方式,產生三個隨機數,但是都是相同的 function f1() { //這個只執行一次 var num=parseInt(Math.random()*10+1); return function () { console.log(num); } } var ff=f1(); ff(); //這裡三個值都是一樣的 ff(); ff(); </script>
運行結果
2、點贊示例
先展示運行結果
代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>點贊應用</title> <style> ul { list-style-type: none; } li { float: left; margin-left: 10px; } img { width: 200px; height: 180px; } input { margin-left: 30%; } </style> </head> <body> <ul> <li><img src="images/ly.jpg" alt=""><br/><input type="button" value="贊(1)"></li> <li><img src="images/lyml.jpg" alt=""><br/><input type="button" value="贊(1)"></li> <li><img src="images/fj.jpg" alt=""><br/><input type="button" value="贊(1)"></li> <li><img src="images/bd.jpg" alt=""><br/><input type="button" value="贊(1)"></li> </ul> <script> //獲取所有的按鈕 //根據標籤名字獲取元素 function my$(tagName) { return document.getElementsByTagName(tagName); } //閉包緩存數據 function getValue() { var value=2; return function () { //每一次點擊的時候,都應該改變當前點擊按鈕的value值 this.value="贊("+(value++)+")"; } } //獲取所有的按鈕 var btnObjs=my$("input"); //循環遍歷每個按鈕,註冊點擊事件 for(var i=0;i<btnObjs.length;i++){ //註冊事件 btnObjs[i].onclick=getValue(); } </script> </body> </html>
參考
1、js閉包的本質
2、JS閉包系列
4、js 閉包
別人罵我胖,我會生氣,因為我心裏承認了我胖。別人說我矮,我就會覺得好笑,因為我心裏知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。 攻我盾者,乃我內心之矛(5)。