JS閉包那些事


      關於閉包,我曾經一直覺得它很討厭,因為它一直讓我很難搞,不過有句話怎么說來着,叫做你越想要一個東西,就要裝作看不起它的樣子。所以,抱着這個態度,我終於擄獲了閉包。

  首先來認識一下什么是閉包,閉包,一共有三大特征:

   1 函數嵌套函數
   2 內部的函數可以引用外部函數的參數和變量
   3 參數和變量不會被垃圾回收機制所收回

舉個栗子:

function aaa(){ 
   var b = 5;
    function bbb(){

        b++; 

        alert(b);

    }

}

aaa(); 

這個栗子就是很明顯的閉包,函數里面嵌套函數,同時內部的函數bbb又可以訪問到外部函數aaa中的變量,至於第三個特征,我們都知道在JS解析機制中,函數內的變量在函數調用完后會被銷毀,但在這里b並沒有被銷毀,因為他還會被里面的函數bbb引用,那么怎么證明呢?

首先,我們要注意,在上面這個栗子中,是不會彈出值的,因為里面的函數bbb,只是聲明了,並沒有被調用,我們都知道,函數它不會主動執行的,那么怎么執行呢,看下面:

function aaa(){ 

    var b = 5;
     function bbb(){ 
          b++;

        alert(b); // 6

     }
    return bbb;

}

var c = aaa(); // 此時aaa被執行 同時把返回結果bbb 賦給c 
c(); //6

將里面的函數作為返回結果,然后可以調用,這種經常會遇見,當aaa執行后,返回結果為一個函數,然后再調用。e而且會彈出6,因為它的值被里面的函數改變。

現在知道了閉包的特征,那最重要的是知道它的好處和應用:

  1 希望一個變量長期駐扎在內存當中

  2 避免全局變量的污染

  3 私用成員的存在

1 首先呢,我們肯定有過這樣的需求,我們需要這樣一個變量,在全局的很多地方都可以被改變,於是我們會聲明一個全局變量,但是也就因為它可以在任何地方被改變,所以很容易出問題被污染。我們既希望它可以不被污染,又希望它可以在很多地方都能訪問到,這樣就閉包就產生了作用。

看個栗子。

function aaa(){ 
     var a = 1;
    return function(){ 
    a++;
    alert(a);
    }
}
var c = aaa();
c(); //2
c(); //3
c(); //4

當我們每調用一次,a的值就被累加,同時它又沒有被污染,因為閉包的第三個特性,它的變量不會被垃圾機制回收,所以每調用一次都會在原來的基礎上加1。

其實這個時候改寫成更簡單的方式,就是改成函數表達式:

var aaa =(function(){
var a = 1;
return function(){ 
a++;
alert(a);
}
})();

aaa(); //2
aaa(); //3

 

這樣是不是省事很多了呢,如果不明白函數表達式,可以參考我前面的文章,講的很清楚。

 

2 我們通過一個例子來看看第三個好處

var aaa =(function(){
  var a = 1;
  function bbb(){ 
    a++;
    alert(a);
  }
 function ccc(){ 
  a++;
  alert(a);
  }
  return { 
    b:bbb,
    c:ccc
  } // 返回json對象

})();

aaa.b(); // 2
aaa.c(); // 3

當在一個函數里聲明了多個函數,可以通過json的格式返回,然后我們就可以在外面這樣調用,這些函數就成了函數aaa的私有成員,如果對json不太了解,可以我看看w3cschool的介紹,很好理解。

3 下面我們看第三個栗子。它非常優秀的體現了閉包的優點,就是我們經常寫的選項卡切換的例子,一般我們會循環給每個列表加上一個索引,通過閉包就不用再加索引了。

在循環中直接找到對應元素的索引。如下 沒循環一次 內部函數調用一次 將i的值直接作為參數傳進去。

window.onload = function(){ 
  var aLi = document.getElementsByTagName('li');
  for(var i=0;i<aLi.length;i++){ 
    (function(i){ 
      aLi[i].onclick = function(){ 
        alert(i);
      }
    })(i);
  }

}

上面代碼就是在點擊事件的函數的外面再加上一個函數,就形成了閉包,然后每循環一次,通過函數表達式的形式,將i傳進去,而通過閉包特性可以知道,事件綁定的函數就可以訪問外面函數里面的參數i。

其實還有另一種寫法:

window.onload = function(){ 
  var aLi = document.getElementsByTagName('li');

  for(var i=0;i<aLi.length;i++){ 
    aLi[i].onclick = (function(i){ 
      return function(){
      alert(i);
      }
    })(i);
  }

}

這種方法的原理是這樣的,首先將一個函數表達式的調用賦給了點擊事件,我們都知道,當事件后面等於的不是函數名,而直接是調用的話,那不點擊,函數也已經執行了,所以在循環的時候,i已經作為參數,傳進了函數表達式,而這個函數表達式的返回值又是一個函數,函數嵌套,閉包關系,這個函數可以訪問外面函數傳進來的每個i。

如果是初學者,不熟悉選項卡,可以先去了解選項卡原理。

閉包需要注意的地方(這個先做簡單了解 后續會補充詳細)

1 在IE下有可能引發內存泄漏 (內存泄漏指當你的頁面跳轉的時候 內存不會釋放 一直占用你的CPU 只有當你關閉了瀏覽器才會被釋放)

內存泄漏產生的條件:

當獲取一個節點或者一組節點 然后又給這個節點添加了屬性( 比如事件) 而在事件內部你又引用外部的東西。

解決;
1 在頁面未加載的時候將添加的屬性去掉

window.onunload = function(){ 
    odiv.onclick = null;
}

2 var id = odiv.id;
odiv.onclick = function(){

  alert(id);
}
odiv = null;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM