標籤:style c class blog code java
目錄:
1. 瞭解閉包前必須知道的概念
2. 閉包的簡要解釋
3. 閉包的應用情境
4. 閉包的另一種寫法
對js的閉包看了很多遍,只是每次都沒有進行深入的瞭解,過去了就忘了。
這次也算比較深入的瞭解了一下吧,不過應該也有很多不足的地方,嘿嘿!
1. 瞭解閉包前必要知道的概念
在我們開始探索閉包前,我們首先應該理解清楚一些必要的概念,這樣對我們之後理解閉包有很大的協助。
每個變數都有其作用的範圍,全域變數全域有效,會一直駐紮在記憶體中;局部變數一般情況下會在函數執行完畢之後,就會銷毀。這裡要提到一個範圍鏈的重要概念,範圍鏈是當代碼在一個環境中執行時建立的,範圍鏈的用途就是要保證執行環境中能有效有序的訪問所有變數和函數。範圍鏈的最前端始終都是當前執行的代碼所在環境的變數對象,下一個變數對象是來自其父親環境,再下一個變數對象是其父親的父親環境,直到全域執行環境。其實,通俗的理解就是:在本範圍內找不到變數或者函數,則在其父親的範圍內尋找,再找不到則到父親的父親範圍內尋找,直到在全域的範圍內尋找!
一般情況下呢,當函數執行完畢後,局部變數就會被銷毀,記憶體中僅儲存這全域範圍。這裡也涉及到了js的記憶體回收機制,在js中有兩種垃圾收集的方式:標記清除和引用計數。標記清除:垃圾收集器在運行時會給儲存在記憶體中的所有變數都加上標記(具體的標記方式暫時就不清楚了),待變數已不被使用或者引用,去掉該標記或添加另一種標記。最後,垃圾收集器完成記憶體清除工作,銷毀那些已無法訪問到的這些變數並回收他們所佔用的空間。
不過,閉包可不是說函數執行完了,變數就回收了。
2. 閉包的簡要解釋
這裡簡單解釋一下閉包的概念:閉包使之有權訪問另一個函數範圍中的變數的函數。建立閉包的常見方式,就是在一個函數的內部建立另一個函數。
首先我們看一個簡單的例子。
function createA(){ var c = 0; return function(){ c++; return c; }}
這就是一個簡單的閉包了: var func = createA(); 從代碼中可以看出,func是一個Function類型的變數,讓我們執行以下:func();結果會發現輸出了1。多次執行以下func()函數,發現輸出的結果一直在增長。這就說明,在func()執行之後,變數c依然儲存在記憶體中,沒有被釋放掉。在return的function中能夠使用變數c我們能夠理解,因為變數c是其父親環境中的變數,在本環境中找不到變數c時就會去父親環境中尋找。
再來看一個例子:
function createB(){ var result = []; for(var i=0; i<10; i++){ result[i] = function(){ return i; }; } return result;}var result = createB();
我們期望的是執行result[0]()能夠返回0,執行result[1]()能夠返回1,以此類推。可是實際上呢,每個函數返回的都是10.這是因為每個函數的範圍鏈中都儲存著createB()函數的使用中的物件,所以它們引用的都是同一個變數i。當createB()函數返回後,變數i的值是10,此時每個函數都引用著儲存變數i的同一個變數對象,所以每個函數內部i的值都是10.不過我們可以通過這樣的設定來讓閉包的行為符合我們的預期。
function createB(){ var result = []; for(var i=0; i<10; i++){ result[i] = function(num){ return function(){ return num; }; }(i); } return result;}var result = createB();
3. 閉包的應用情境
(1)在記憶體中維持一個變數。比如前面講的小例子,由於閉包,函數createA中的c會一直存在於記憶體中,因此每次執行func(),都會給變數c加1.
(2)保護函數內的變數安全。還是以最開始的例子,函數createA()中的變數c只有內部的函數才能訪問,而無法通過其他途徑訪問到,因此保護了變數c的安全。
(3)實現物件導向中的對象。javascript並沒有提供類這樣的機制,但是我們可以通過閉包來類比出類的機制,不同的對象執行個體擁有獨立的成員和狀態。
這裡我們看一個例子:
var student = function(){ var name = "bing"; var score = 80; return { getName:function(){ return name; }, setName:function(thisName){ name = thisName; } }}();
分別執行下面的語句:
student.name;student.getName();student.setName("zhongguo");student.getName();
可以看到,變數student是不能直接存取變數name的。只能通過getName和setName來對變數name進行讀寫操作。
針對第三點,我們看這樣的一個例子。
function Student(){ var name = "bing"; return { getName:function(){ return name; }, setName:function(thisName){ name = thisName; } }}
分別建立兩個對象stu1,stu2:
var stu1 = Student();stu1.setName("beijing");stu1.getName();var stu2 = Student();stu2.setName("shanghai");stu2.getName();stu1.getName();
輸出的結果分別是:"beijing", "shanghai", "beijing"。可以發現stu1,stu2這兩個對象之間相互獨立,互不影響。
4. 閉包的另一種寫法
在jQuery中我們經常見這樣的寫法:
(function(x, y){ })(3, 5);
這是立即執行的匿名函數,匿名函數也是一種閉包。我們來看這個:
for(var i=0; i<10; i++) { (function(i){ setTimeout(function(){ console.log(i); },i*1000) })(i);}
這段代碼是每隔1000ms依次輸出:0, 1, 2, 3, 4, 5, 6, 7, 8, 9
如果我們不使用匿名函數,直接在for迴圈裡寫setTimeout會是什麼結果呢,這就回到了第二部分討論的問題,setTimeout也是一個函數呀,他使用了外部環境的變數i,因此每隔1000ms輸出一個10,最後輸出十個10。