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閉包系列

3、JavaScript閉包

4、js 閉包

別人罵我胖,我會生氣,因為我心裏承認了我胖。別人說我矮,我就會覺得好笑,因為我心裏知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。  攻我盾者,乃我內心之矛(5)。