javascript中的閉包這一篇就夠了

什麼是閉包

維基百科中的概念javascript

  • 在計算機科學中,閉包(也稱詞法閉包或函數閉包)是指一個函數或函數的引用,與一個引用環境綁定在一塊兒,這個引用環境是一個存儲該函數每一個非局部變量(也叫自由變量)的表。
  • 閉包,不一樣於通常的函數,它容許一個函數在當即詞法做用域外調用時,仍可訪問非本地變量

學術上前端

  • 閉包是指在 JavaScript 中,內部函數老是能夠訪問其所在的外部函數中聲明的參數和變量,即便在其外部函數被返回return掉(壽命終結)了以後。

我的理解java

  • 閉包是在函數裏面定義一個函數,該函數能夠是匿名函數,該子函數可以讀寫父函數的局部變量。

閉包的常見案例分析

案例分析是從淺入深但願你們都看完!node

  • 案例1---基本介紹:

web

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);    
    });
}();
複製代碼

前端原始點擊事件操做也用到了閉包來訪問外部的局部變量。ajax

  • 案例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";
<span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">return</span> {    
   <span class="hljs-attr" style="line-height: 26px; color: #9CDCFE;">getName</span> : <span class="hljs-function" style="line-height: 26px; color: #DCDCDC;"><span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">function</span>(<span class="hljs-params" style="line-height: 26px; color: #DCDCDC;"></span>)</span>{    
       <span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">return</span> name;    
   },    
   <span class="hljs-attr" style="line-height: 26px; color: #9CDCFE;">setName</span> : <span class="hljs-function" style="line-height: 26px; color: #DCDCDC;"><span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">function</span>(<span class="hljs-params" style="line-height: 26px; color: #DCDCDC;">newName</span>)</span>{    
       name = newName;    
   }    
}    
複製代碼

}();

print(person.name);//直接訪問,結果爲undefined print(person.getName());
person.setName("kaola");
print(person.getName());

獲得結果以下:

複製代碼<span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">return</span> { <span class="hljs-attr" style="line-height: 26px; color: #9CDCFE;">getName</span> : <span class="hljs-function" style="line-height: 26px; color: #DCDCDC;"><span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">function</span>(<span class="hljs-params" style="line-height: 26px; color: #DCDCDC;"></span>)</span>{ <span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">return</span> name; }, <span class="hljs-attr" style="line-height: 26px; color: #9CDCFE;">setName</span> : <span class="hljs-function" style="line-height: 26px; color: #DCDCDC;"><span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">function</span>(<span class="hljs-params" style="line-height: 26px; color: #DCDCDC;">newName</span>)</span>{ name = newName; } } 複製代碼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; },
   <span class="hljs-attr" style="line-height: 26px; color: #9CDCFE;">clearSearchBox</span> : <span class="hljs-function" style="line-height: 26px; color: #DCDCDC;"><span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">function</span>(<span class="hljs-params" style="line-height: 26px; color: #DCDCDC;">dsid</span>)</span>{    
       <span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">if</span>(dsid <span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">in</span> cache){    
          cache[dsid].clearSelection();      
       }    
   }    
};    
複製代碼

})();

複製代碼<span class="hljs-attr" style="line-height: 26px; color: #9CDCFE;">clearSearchBox</span> : <span class="hljs-function" style="line-height: 26px; color: #DCDCDC;"><span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">function</span>(<span class="hljs-params" style="line-height: 26px; color: #DCDCDC;">dsid</span>)</span>{ <span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">if</span>(dsid <span class="hljs-keyword" style="line-height: 26px; color: #569CD6;">in</span> 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. 對於閉包外部沒法引用它內部的變量,所以在函數內部建立的變量執行完後會馬上釋放資源,不污染全局對象。
  5. 閉包使用的時候要考慮到內存泄漏,由於不釋放外部引用,可是合理的使用閉包是內存使用不是內存泄漏。
以爲本文對你有幫助?請分享給更多人

歡迎你們關注個人公衆號——程序員成長指北。請自行微信搜索——「程序員成長指北」

相關文章
相關標籤/搜索