1. 程式人生 > >JS閉包的理解(轉載)

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);
  }

}

上面程式碼就是在點選事件的函式的外面再加上一個函式(    立即執行函式 (function(){…})()    ),就形成了閉包,然後每迴圈一次,通過函式表示式的形式,將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;