javascript中的閉包這一篇就夠了
什麼是閉包
維基百科中的概念
- 在電腦科學中,閉包(也稱詞法閉包或函式閉包)是指一個函式或函式的引用,與一個引用環境繫結在一起,這個引用環境是一個儲存該函式每個非區域性變數(也叫自由變數)的表。
- 閉包,不同於一般的函式,它允許一個函式在立即詞法作用域外呼叫時,仍可訪問非本地變數
學術上
- 閉包是指在 JavaScript 中,內部函式總是可以訪問其所在的外部函式中宣告的引數和變數,即使在其外部函式被返回return掉(壽命終結)了之後。
個人理解
- 閉包是在函式裡面定義一個函式,該函式可以是匿名函式,該子函式能夠讀寫父函式的區域性變數。閉包它實現了外部作用域訪問內部作用域中變數的方法。
閉包的常見案例分析
案例分析是從淺入深希望大家都看完!
- 案例1—基本介紹:
function A(){
var localVal=10;
return localVal;
}
A();//輸出30
function A(){
var localVal=10;
return function(){
console.log(localVal);
return localVal;
}
}
var func=A();
func();//輸出10
兩段程式碼,在第二段程式碼中,函式A內的匿名函式可以訪問到函式A中的區域性變數這就是閉包的基本使用。
- 案例2—前端實現點選事件
!function(){
var localData="localData here";
document.addEventListener('click',function(){
console.log(localData);
});
}();
前端原始點選事件操作也用到了閉包來訪問外部的區域性變數。
- 案例3—ajax請求
!function(){ var localData="localData here"; var url="http://www.baidu.com"; $.ajax({ url:url, success:function(){ //do sth... console.log(localData); } }) }();
在ajax請求的方法中也用到了閉包,訪問外部的區域性變數。
- 案例4—for迴圈案例
var arrays = [];
for (var i=0; i<3; i++) {
arrays.push(function() {
console.log('>>> ' + i); //all are 3
});
}
上面的這段程式碼,剛看了程式碼一定會以為陸續打印出1,2,3,實際輸出的是3,3,3,出現這種情況的原因是匿名函式儲存的是引用,當for迴圈結束的時候,i已經變成3了,所以列印的時候變成3。出現這種情況的解決辦法是利用閉包解決問題。
for (var i=0; i<3; i++) {
(function(n) {
tasks.push(function() {
console.log('>>> ' + n);
});
})(i);
}
閉包裡的匿名函式,讀取變數的順序,先讀取本地變數,再讀取父函式的區域性變數,如果找不到到全局裡面搜尋,i作為區域性變數存到閉包裡面,所以調整後的程式碼可以能正常列印1,2,3。
閉包與記憶體洩漏
- javascript回收後記憶體的方式:
javascript的主要通過計數器方式回收記憶體,假設有a,b,c三個物件,當a引用b的時候,那麼b的引用計算器增加1(通俗的說用到那個物件哪個物件引用計算器增加1),同時b引用c的時候,c引用計數器增加1,當a被釋放的時候,b的引用計數器減少1,變成0的時候這個物件被釋放,c計數器變成0,被釋放,但是當遇到b和c之間互相引用的時候,無法通過計數器方式釋放記憶體。
- 閉包可以導致上面所說b和c互相引用無法釋放記憶體
第一個案例的程式碼拿過來分析:
function A(){
var localVal=10;
return function(){
console.log(localVal);
return localVal;
}
}
var func=A();
func();//輸出10
當A函式結束的時候,想要釋放,發現它的localVal變數被匿名函式引用,所有A函式無法釋放,導致記憶體洩漏。但是也有好處,閉包正是可以做到這一點,因為它不會釋放外部的引用,從而函式內部的值可以得以保留。
說明:閉包不代表一定會帶來記憶體洩漏,良好的使用閉包是不會造成記憶體洩漏的。
閉包的應用
- 封裝
var person = function(){
//變數作用域為函式內部,外部無法訪問
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接訪問,結果為undefined
print(person.getName());
person.setName("kaola");
print(person.getName());
得到結果如下:
undefined
default
kaola
- 例項中的for迴圈另一種形式
doucument.body.innerHTML="<div id=div1>aaa</div>"+"<div id=div2>bbb</div>"+"<div id=div3>ccc</div>";
for(var i=1;i<4;i++){
!function(i){
document.getElementById('div'+i);
addEventListener('click',function(){
alert(i);//1,2,3
});
}
}
- 結果快取
var CachedSearchBox = (function(){
var cache = {},
count = [];
return {
attachSearchBox : function(dsid){
if(dsid in cache){//如果結果在快取中
return cache[dsid];//直接返回快取中的物件
}
var fsb = new uikit.webctrl.SearchBox(dsid);//新建
cache[dsid] = fsb;//更新快取
if(count.length > 100){//保正快取的大小<=100
delete cache[count.shift()];
}
return fsb;
},
clearSearchBox : function(dsid){
if(dsid in cache){
cache[dsid].clearSelection();
}
}
};
})();
CachedSearchBox.attachSearchBox("input");
說明:開發中會碰到很多情況,設想我們有一個處理過程很耗時的函式物件,每次呼叫都會花費很長時間,那麼我們就需要將計算出來的值儲存起來,當呼叫這個函式的時候,首先在快取中查詢,如果找不到,則進行計算,然後更新快取並返回值,如果找到了,直接返回查詢到的值即可。閉包正是可以做到這一點,因為它不會釋放外部的引用,從而函式內部的值可以得以保留。
面試題分析
閉包測試題: 求輸出結果
function fun(n,o){
console.log(o);
return {
fun:function(m){//[2]
return fun(m,n);//[1]
}
}
}
var a=fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
var b=fun(0).fun(1).fun(2).fun(3);
var c=fun(0).fun(1);
c.fun(2);
c.fun(3);
分析內容說明,在看這篇文章的時候,注意兩點可能會看的更明白:
- JS的詞法作用域,JS變數作用域存在於函式體中即函式體,並且變數的作用域是在函式定義宣告的時候就是確定的,而非在函式執行時。
- 在JS中呼叫函式的時候,如果用一個引數的方法呼叫兩個引數的方法,這時候只是第二個引數未定義,程式碼不會報錯停止執行,正常流程往下走,像面試題中仍然會返回一個物件。
總結
- 閉包其實是在函式內部定義一個函式。
- 閉包在使用的時候不會釋放外部的引用,閉包函式內部的值會得到保留。
- 閉包裡面的匿名函式,讀取變數的順序,先讀取本地變數,再讀取父函式的區域性變數。
- 對於閉包外部無法引用它內部的變數,因此在函式內部建立的變數執行完後會立刻釋放資源,不汙染全域性物件。
- 閉包使用的時候要考慮到記憶體洩漏,因為不釋放外部引用,但是合理的使用閉包是記憶體使用不是記憶體洩漏。