標籤:
本文採用5W1H分析法來看一下閉包。
一、why-----從為什麼要引入閉包先來瞭解一下閉包。
討論為什麼要引入閉包就要先討論一下js中的範圍鏈及記憶體回收機制。
熟悉js語言的人都知道js中有範圍的概念和記憶體回收機制。那麼我們首先來看一下js中的範圍鏈
- 範圍鏈:
js中的變數執行環境分為全域執行環境和局部執行環境。當代碼在執行時會建立變數對象的一個範圍鏈。而範圍鏈簡單來說,就是函數在定義的時候建立的上下文執行環境,用於在標識符解析中變數尋找。範圍鏈的前端始終都是當前執行代碼所在環境的變數對象即函數自身的本地變數,範圍鏈的下一個變數對象來自包含環境即父級函數,而在下一個變數對象則來自下一個包含環境,然後一直延續到全域執行環境;全域執行環境的變數對象始終是範圍鏈的最後一個對象。js中標識符解析就是沿著範圍鏈一級一級地搜尋標識符的過程;搜尋過程從範圍鏈的前端開始逐級向上搜尋,直到找到標識符為止。
範圍鏈只能從下往上尋找而不能從上往下尋找,舉例來說函數內部可以訪問到全域變數,而在函數外部不能訪問函數內部變數。如下代碼:
var n=999;function f1(){ alert(n);}f1(); // 999
代碼中n為全域變數,而在函數內部可以直接存取到。再看以下代碼:
function f1(){ var n=999;}alert(n); // 會報錯 n is not defined
當在函數外部存取內部變數時會報錯。瞭解了範圍鏈再來看一下js記憶體回收機制。
2.js中記憶體回收機制:
javascript時一門具有自動記憶體回收機制的語言。一般來說,一個函數在執行開始的時候,會給其中定義的局部變數劃分記憶體空間以便儲存他們的值,然後在函數中使用變數,等到函數執行完畢返回了,局部變數就沒有存在的必要了,這些變數就被認為是無用的了;因此釋放他們的記憶體供將來使用,下次再執行此函數的時候,所有的變數又回到最初的狀態,重新賦值使用。由於範圍鏈和記憶體回收機制的限制,若需要在函數外部存取函數內部的變數則訪問不到。若要想在函數外部存取函數內部的變數怎麼訪問呢?此時閉包就華麗登場了(*^_^*)。
二、what-----什麼是閉包呢從概念來理解
《javascript進階程式設計第三版》中是這樣定義閉包的“閉包是指有權訪問另一個函數範圍中的變數的函數”。所有的函數都可以說是閉包。在我理解,簡單來說就是在一個函數內部嵌套一個函數,內建函式可以引用外部函數的參數和變數,參數和變數不會被回收,使局部變數能夠在全域中訪問到,暴露局部變數能夠讓他的上一層環境訪問到。閉包由函數和建立該函數的執行環境構成。當在函數內部嵌套另外一個函數時,並且外部函數將嵌套的函數對象作為傳回值返回就是閉包的一種。如下代碼執行個體:
function aaa() { var a=1; function bbb(){ a++; alert(a); } return bbb; }
var c=aaa();
c();//2
內建函式bbb可以訪問到外部函數中的a變數。當外部函數執行完後,由於內建函式引用了外部函數的變數,因此內建函式的範圍包含了外部函數的使用中的物件,當外部函數被調用返回後其執行環境被銷毀,但內建函式的範圍鏈依然存在,因其內建函式還未被銷毀所以其外部函數的使用中的物件仍然在記憶體中,如在bbb中訪問a變數依然可以訪問到。等其內建函式執行完後範圍鏈被銷毀,釋放變數記憶體。瞭解了閉包的概念,再來看一下閉包的應用情境。
三、when------什麼時候使用閉包?
當希望一個變數長期駐紮在記憶體中時,此時使用閉包可以使函數執行完後其使用中的物件仍然在記憶體中。
四、where----閉包應用在哪?
閉包好處:1、避免全域變數的汙染2、可以模仿塊級範圍3、私人成員的存在
1、閉包可以用來模仿塊級範圍。
js中本身沒有塊級範圍的概念,而使用閉包可以用來模仿塊級範圍。
function outputNumbers(count){ (function(){ for(var i=0;i<count;i++){ alert(i); } })(); alert(i);//報錯 i is not defined }
在for迴圈外部插入一個私人範圍,變數i只能在迴圈中使用,使用後即被銷毀。所以在私人範圍外部存取不到。
2、在迴圈中直接找到對應的索引。
若頁面中有多個li標籤,而需要給每個標籤添加一個事件,則可以這樣做:
for(var i=0;i<aLi.length;i++){ (function(i){ aLi[i].onclick=function(){ alert(i); } })(i); }
如果不使用閉包的話,那麼彈出的i為aLi.length的值。
for(var i=0;i<6;i++){ aLi[i].onclick=function(){ alert(i);//6 } }
3、用閉包類比私人方法:
js中本身沒有私人成員的概念,所有對象屬性都是公有的。閉包可以用來建立私人變數。如下代碼:
var aaa = (function(){ var a = 1; function bbb(){ a++; alert(a); } function ccc(){ a++; alert(a); } return { b:bbb, //json結構 c:ccc }})();aaa.b(); //2aaa.c()
4、模組化代碼,避免全域變數的汙染:
var abc = (function(){ //abc為外部匿名函數的傳回值 var a = 1; return function(){ a++; alert(a); }})();abc(); //2 調用一次abc函數,其實是調用裡面內建函式的傳回值 abc(); //3
接下來再來看一下閉包的缺點:閉包會使變數始終儲存在記憶體中,如果不當使用會增大記憶體消耗。還有由於IE的js對象和DOM對象使用不同的垃圾收集方法,因此閉包在IE中會導致記憶體泄露問題,也就是無法銷毀駐留在記憶體中的元素。只有關閉瀏覽器時才會釋放記憶體。如下代碼:
function fn(){ var oDiv = document.getElementById(‘div1‘);//oDiv用完之後一直駐留在記憶體中 oDiv.onclick = function () { alert(oDiv.id);//這裡用oDiv導致記憶體泄露 };}fn();//最後應將oDiv解除引用來避免記憶體泄露function fn(){ var oDiv = document.getElementById(‘div1‘); oDiv.onclick = function () { alert(oDiv.id); }; oDiv = null;}
由於oDiv.onclick中引用了oDiv.id,互相引用時存在記憶體流失,因此需手動解除引用來避免記憶體流失。
js之認識閉包