閉包在javascript來講是比較重要的概念,平時工做中也是用的比較多的一項技術。下來對其進行一個小小的總結javascript
官方說法:java
閉包是指有權訪問另外一個函數做用域中的變量的函數。建立閉包的常見方式,就是在一個函數內部建立另外一個函數,經過另外一個函數訪問這個函數的局部變量------《javascript高級程序設計第三版》node
下面就是一個簡單的閉包:瀏覽器
function A(){
var text="hello world";
function B(){
console.log(text);
}
return B;
}
var c=A();
c(); // hello world
按照字面量的意思是:函數B有權訪問函數A做用域中的變量(text),經過另外一個函數C來訪問這個函數的局部變量text。所以函數B造成了一個閉包。也能夠說C是一個閉包,由於C執行的實際是函數B。緩存
這個須要注意的是,直接執行A();是沒有任何反應的。由於return B沒有執行,除非是return B();閉包
閉包有三個特性:函數
1.函數嵌套函數
2.函數內部能夠引用外部的參數和變量
3.參數和變量不會被垃圾回收機制回收
解釋一下第3點,爲何閉包的參數和變量不會被垃圾回收機制回收呢?
首先咱們先了解一下javascript的垃圾回收原理:
(1)、在javascript中,若是一個對象再也不被引用,那麼這個對象就會被GC(garbage collection)
回收; this
(2)、若是兩個對象互相引用,而再也不被第3
者所引用,那麼這兩個互相引用的對象也會被回收。spa
上面的示例代碼中A是B的父函數,而B被賦給了一個全局變量C(全局變量的生命週期直至瀏覽器卸載頁面纔會結束),這致使B始終在內存中,而B的存在依賴於A,所以A也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。設計
其實閉包的做用也是有閉包的特性決定的,根據上面的閉包特性,閉包的做用以下:
一、能夠讀取函數內部的變量,而不是定義一塊兒全局變量,避免污染環境
二、讓這些變量的值始終保持在內存中。
下面主要介紹幾種常見的閉包,並進行解析:
demo1 局部變量的累加。
function countFn(){
var count=1;
return function(){ //函數嵌套函數
count++;
console.log(count);
}
}
var y = countFn(); //外部函數賦給變量y;
y(); //2 //y函數調用一次,結果爲2,至關於countFn()()
y(); //3 //y函數調用第二次,結果爲3,由於上一次調用的count還保存在內存中,沒有被銷燬,因此實現了累加
y=null; //垃圾回收,釋放內存
y(); // y is not a function
因爲第一次執行完,變量count還保存在內存中,因此不會被回收,以至於第二次執行的時候能夠對上次的值就行累加。當引入y=null時,銷燬引用,釋放內存
demo2 循環中使用閉包
代碼以下(下面的三個代碼示例):咱們的目的是想在每次循環中調用循環序號:
demo2-1
for (var i = 0; i < 10; i++) { var a = function(){ console.log(i) } a() //依次爲0--9 }
這個例子的結果是沒有題的,咱們依次打印出了0-9
每一層匿名函數和變量i都組成了一個閉包,可是這樣在循環中並無問題,由於函數在循環體中當即被執行了
demo2-2
可是在setTimeout中就不同了
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i); //10次10
}, 1000);
}
咱們指望的依次是打印出0--10,實際狀況是打印出 10次10。即便吧setTimeout的時間改成0,也是打印出10個10。這是爲何呢?
這是由於setTimeout
的一種機制,setTimeout
是從任務隊列結束的時候開始計時的,若是前面有進程沒有結束,那麼它就等到它結束再開始計時。在這裏,任務隊列就是它本身所在的循環。
循環結束setTimeout
纔開始計時,因此不管如何,setTimeout
裏面的i都是最後一次循環的 i。該代碼中,最後的 i 爲10,因此打印出了10個10.
這也就是爲何setTimeout
的回調不是每次取循環時的值,而取最後一次的值
demo2-3
解決上面的setTimeout不能依次打印出循環的問題
for(var i=0;i<10;i++){ var a=function(e){ return function(){ console.log(e); //依次輸入0--9
} } setTimeout(a(i),0); }
由於setTimeout
第一個參數須要一個函數,因此返回一個函數給它,返回的同時把 i 做爲參數傳進去,經過形參 e 緩存了i,也就是說e變量至關因而 i 的一個拷貝 ,並帶進返回的函數裏面。
當 setTimeout
的執行時,它就擁有了對 e
的引用,而這個值是不會被循環改變的。
也能夠用下面的寫法,和上面相似:
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); //依次打印出0-9
}, 0); })(i); }
demo3 循環中添加事件
看下面的一個典型的demo.
咱們但願每次點擊li的時候,alert出li的索引值,因此用下面的代碼:
<ul id="test">
<li>第一個</li>
<li>第二個</li>
<li>第三個</li>
<li>第四個</li>
</ul>
var nodes = document.getElementsByTagName("li"); for(i = 0,len=nodes.length;i<len;i++){ nodes[i].onclick = function(){ alert(i); //值全是4
}; }
事與願違,不管點擊哪個li,都是alert(4),也就是都是alert循環結束以後的索引值。這是爲何呢?
這是由於循環中爲不一樣的元素綁定事件,事件回調函數裏若是調用了跟循環相關的變量,則這個變量取循環的最後一個值。
因爲綁定的回調函數是一個匿名函數,因此上面的代碼中, 這個匿名函數是一個閉包,攜帶的做用域爲外層做用域(也就是for裏面的做用域),當事件觸發的時候,做用域中的變量已經隨着循環走到最後了。
還有一點就是,事件是須要觸發的,而絕大多數狀況下,觸發的時候循環已經結束了,因此循環相關的變量就是最後一次的取值。
要實現點擊li,alert出li的索引值,須要將上面的代碼進行如下的修改:
<ul id="test">
<li>第一個</li>
<li>第二個</li>
<li>第三個</li>
<li>第四個</li>
</ul>
var nodes=document.getElementsByTagName("li"); for(var i=0;i<nodes.length;i++){ (function(e){ nodes[i].onclick=function(){ alert(e); }; })(i) }
解決思路: 增長若干個對應的閉包域空間(這裏採用的是匿名函數),專門用來存儲原先須要引用的內容(下標)。
噹噹即執行函數執行的時候,e 值不會被銷燬,由於它的裏面有個匿名函數(也能夠說是由於閉包的存在,因此變量不會被銷燬)。執行後,e 值 與全局變量 i 的聯繫就切斷了,
也就是說,執行的時候,傳進的 i 是多少,當即執行函數的 e 就是多少,可是 e 值不會消失,由於匿名函數的存在。
也能夠用下面的解法,原理是同樣的:
<ul id="test">
<li>第一個</li>
<li>第二個</li>
<li>第三個</li>
<li>第四個</li>
</ul>
var nodes=document.getElementsByTagName('li'); for(var i = 0; i<nodes.length;i++){ (function(){ var temp = i; nodes[i].onclick = function () { alert(temp); } })(); }
一、形成內存泄露
因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多,因此只有在絕對必要時再考慮使用閉包。
二、在閉包中使用this也可能會致使一些問題。
代碼示例:來源於《js高級程序設計3》;
其實咱們的目的是想alert出object裏面的name
var name="The Window"; var object={ name:"My Object", getNameFunc:function(){ return function(){ return this.name; } } } alert(object.getNameFunc()()); // The Window
由於在全局函數中,this等於window,而當函數被做爲某個對象的方法調用時,this等於那個對象。不過,匿名函數的執行環境具備全局性,所以其this對象一般指向window。
每一個函數在被調用時,都會自動取的兩個特殊變量:this和 arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止。也就是說,裏面的return function只會搜索
到全局的this就中止繼續搜索了。由於它永遠不可能直接訪問外部函數中的這兩個變量。
稍做修改,把外部做用域中的this對象保存在一個閉包可以訪問的變量裏。這樣就可讓閉包訪問該對象了。
var name="The Window"; var object={ name:"My Object", getNameFunc:function(){ var that=this; return function(){ return that.name; } } } alert(object.getNameFunc()()); // My Object
咱們把this對象賦值給了that變量。定義了閉包以後閉包也能夠訪問這個變量。所以,即便在函數返回以後,that也仍引用這object,因此調用object.getNameFunc()()就返回 「My Object」了。
當在函數內部定義了其餘函數,就建立了閉包。閉包有權訪問包含函數內部的全部變量。
閉包的做用域包含着它本身的做用域、包含函數的做用域和全局做用域。
當函數返回一個閉包時,這個函數的做用域會一直在內存中保存到閉包不存在爲止。
使用閉包必須維護額外的做用域,全部過分使用它們可能會佔用大量的內存
有誤之處,歡迎指出