1. 程式人生 > >javascript中的閉包這一篇就夠了

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中呼叫函式的時候,如果用一個引數的方法呼叫兩個引數的方法,這時候只是第二個引數未定義,程式碼不會報錯停止執行,正常流程往下走,像面試題中仍然會返回一個物件。

總結

  1. 閉包其實是在函式內部定義一個函式。
  2. 閉包在使用的時候不會釋放外部的引用,閉包函式內部的值會得到保留。
  3. 閉包裡面的匿名函式,讀取變數的順序,先讀取本地變數,再讀取父函式的區域性變數。
  4. 對於閉包外部無法引用它內部的變數,因此在函式內部建立的變數執行完後會立刻釋放資源,不汙染全域性物件。
  5. 閉包使用的時候要考慮到記憶體洩漏,因為不釋放外部引用,但是合理的使用閉包是記憶體使用不是記憶體洩漏。