javascript 閉包

名詞解釋

官方的解釋能夠無視了,比繞口令還要繞口令的拗口的詞彙拼接。
我的理解就是在函數中嵌套函數。javascript

前提

理解閉包必須先理解另外另個內容:1.做用域鏈 2.垃圾回收html

做用域鏈

js中有兩種變量,一種叫局部變量,一種叫全局變量。java

沒有寫在function內部的都是全局變量,寫在function內部的都是局部變量,爲了不本身定義的變量名污染全局變量名(尤爲是如今調用的js控件愈來愈多的年代),所以不多會定義全局變量。數組

由於局部變量就是聲明在function內部的變量,所以這個function就是該變量的做用域。也就是這個function內部,包括這個function的兒子(子函數)、孫子(子函數內的子函數),都可以訪問到。這也就是說,這個function內部的子函數也能夠訪問到這個變量。閉包

var f = function(){
    // f就是a的做用域
    var a = 999;
    
    function f1(){
        // 所以,即便在f的內部嵌套函數(子函數)中也能夠訪問到a
        alert(a);
    }
    f1();
    
};

f();

垃圾回收機制

在函數執行的時候,js的解釋器會給全部須要用到的變量分配一個內存空間,這樣一直到這個函數執行結束,就會認爲這些變量已經沒有用了,就會啓用垃圾回收機制來把這些內存回收。函數

這樣在遇到函數嵌套時,如上面例子所示,在f執行完成以後,變量a由於還要被f1這個內部嵌套函數繼續使用,所以a並不會被釋放,所以,在f1()的結果是999
也就是說,在js解析器發現函數中嵌套了函數時,就會把函數中的變量和子函數中的變量一塊兒保存在內存中,也就是構建了一個「閉包」。這些變量不會被內存回收器回收,只有當內部嵌套函數再也不執行以後,纔會被回收。優化

閉包案例

屬性

var person = function(){

        var name = "張三";

        this.getName=function(){
          return name;
        };
      };

      var p = new person();
      alert(p.getName());

上面的代碼裏,name這個屬性只能經過調用getName這個方法才能獲取,這是必包的用法之一。this

保存複雜計算中的臨時變量

經典案例:code

html:
    <ul>
        <li>aaa</li>
        <li>bbb</li>
        <li>ccc</li>
        <li>ddd</li>

想要實現的效果是點擊li的時候,alert各自的內容,
好比點擊第一個,alert 「aaa」
不少人第一反應的代碼以下:htm

var foo = function(){
            var lis = document.getElementsByTagName("li");
            for (var i= 0;i<lis.length;i++){
                lis[i].onclick = function(){
                    alert(i);
                };
            }
        };
        foo();

而後會發現結果和想象中徹底不同,點擊任何一個都是返回4。
這是由於在賦值的時候,傳的i是對內存地址對引用。當循環結束後,i指向對就是4.所以,全部的li在點擊時都是alert(4)。

在這個時候就要用到閉包了。在每一次循環的時候,都把當前的i經過當即執行函數賦值。
以下所示:

<script type="text/javascript">
        "use strict"
        var foo = function(){
            var liList = document.getElementsByTagName("li"),
            i=0,
            max = liList.length;
            for(;i<max;i++){
                (function(index){
                    liList[index].onclick = function(){
                        alert(liList[index].innerHTML);
                    };  
                })(i);
            }
        };
        foo();

    </script>

用閉包實現Fibonacci數列

話很少說,直接上代碼

var fibonacci = function(n){
            // 將斐波那契數列放在這個臨時變量中
            var arr = [0,1,1];
            var fi = function(m){
                // 若是存在則直接返回
                if(arr[m]){
                    return arr[m];
                }else {
                    // 若是不存在則遞歸獲取
                    arr[m] = fi(m-1)+fi(m-2);
                    return arr[m];
                }
            };
            return fi(n);
        };
        alert(fibonacci(10));

上面的代碼若是不用閉包來作,那麼arr數組將不能一直保存在內存中,每次遞歸都會重置了。

固然,以上代碼能夠進一步優化:

var fibonacci = (function(){
            var arr = [0,1,1];
            return function(n){
                if(arr[n]){
                    return arr[n];
                } else {
                    arr[n] = fibonacci(n-1)+fibonacci(n-2);
                    return arr[n];
                }
            };
        })();

內部嵌套函數返回這樣一個匿名函數:

function(n){
                if(arr[n]){
                    return arr[n];
                } else {
                    arr[n] = fibonacci(n-1)+fibonacci(n-2);
                    return arr[n];
                }
            }

所以主函數要當即執行才能返回結果。

其餘demo(閉包的高級應用)

12個按鈕,顯示"嗚嗚",按下哪一個,變成"哈哈",其餘繼續變回"嗚嗚"

<script type="text/javascript">
        "use strict"
        var btns = document.getElementsByTagName("button"),
        i=0,
        btnLength = btns.length;
        
        for(;i<btnLength;i++){
            (function(k){
                btns[i].onclick = function(){
                    this.innerHTML = "哈哈";
                    for(var j=0; j<btnLength;j++){
                        if(k!=j){
                            btns[j].innerHTML = "嗚嗚";
                        }
                    }
                };
            })(i);
        }

    </script>

上面這種寫法雖然可行,可是每次點擊都要循環12次,效率過低,能夠進一步使用閉包。

<script type="text/javascript">
      "use strict"

      var load = function(){
        var btns = document.getElementsByTagName("button"),
        i=0,
        btnLength = btns.length,
        current;
        for(;i<btnLength;i++){
          (function(k){
            btns[i].onclick = function(){
             if(current){
              current.innerHTML = "嗚嗚";
            }
            this.innerHTML = "哈哈";
            current = this;
          };
        })(i);
      }
    };
    window.onload = load();
  </script>

這樣寫簡直太巧妙了。。。 先在閉包中建立current這個變量,而後若是current存在,則說明這不是第一次點擊了,那麼current中保存的就是上一次點擊的那個按鈕。因此只要把上一次點擊的按鈕變回「嗚嗚」便可。 而後再把此次的先變爲哈哈,再存到current中。

相關文章
相關標籤/搜索