什麼是閉包
維基百科中的概念
- 在計算機科學中,閉包(也稱詞法閉包或函數閉包)是指一個函數或函數的引用,與一個引用環境綁定在一起,這個引用環境是一個存儲該函數每個非局部變量(也叫自由變量)的表。
- 閉包,不同于一般的函數,它允許一個函數在立即詞法作用域外調用時,仍可通路非本地變量
學術上
- 閉包是指在 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);
由于分析内容比較多,大家可直接參考這篇文章 https://cnodejs.org/topic/567ed16eaacb6923221de48f
分析内容說明,在看這篇文章的時候,注意兩點可能會看的更明白:
- JS的詞法作用域,JS變量作用域存在于函數體中即函數體,并且變量的作用域是在函數定義聲明的時候就是确定的,而非在函數運作時。
- 在JS中調用函數的時候,如果用一個參數的方法調用兩個參數的方法,這時候隻是第二個參數未定義,代碼不會報錯停止運作,正常流程往下走,像面試題中仍然會傳回一個對象。
總結
- 閉包其實是在函數内部定義一個函數。
- 閉包在使用的時候不會釋放外部的引用,閉包函數内部的值會得到保留。
- 閉包裡面的匿名函數,讀取變量的順序,先讀取本地變量,再讀取父函數的局部變量。
- 對于閉包外部無法引用它内部的變量,是以在函數内部建立的變量執行完後會立刻釋放資源,不污染全局對象。