天天看點

到底什麼是閉包,學習這一篇就夠了

什麼是閉包

維基百科中的概念
  • 在計算機科學中,閉包(也稱詞法閉包或函數閉包)是指一個函數或函數的引用,與一個引用環境綁定在一起,這個引用環境是一個存儲該函數每個非局部變量(也叫自由變量)的表。
  • 閉包,不同于一般的函數,它允許一個函數在立即詞法作用域外調用時,仍可通路非本地變量
學術上
  • 閉包是指在 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中調用函數的時候,如果用一個參數的方法調用兩個參數的方法,這時候隻是第二個參數未定義,代碼不會報錯停止運作,正常流程往下走,像面試題中仍然會傳回一個對象。

總結

  1. 閉包其實是在函數内部定義一個函數。
  2. 閉包在使用的時候不會釋放外部的引用,閉包函數内部的值會得到保留。
  3. 閉包裡面的匿名函數,讀取變量的順序,先讀取本地變量,再讀取父函數的局部變量。
  4. 對于閉包外部無法引用它内部的變量,是以在函數内部建立的變量執行完後會立刻釋放資源,不污染全局對象。

繼續閱讀