深刻淺出JavaScript之閉包(Closure)

閉包(closure)是掌握Javascript從人門到深刻一個很是重要的門檻,它是Javascript語言的一個難點,也是它的特點,不少高級應用都要依靠閉包實現。下面寫下個人學習筆記~javascript

閉包-無處不在 

在前端編程中,使用閉包是很是常見的,咱們常常有意無心,直接或間接用到了閉包。閉包可使傳遞數據更加靈活(好比處理一些點擊事件)html

1
2
3
4
5
6
7
! function () {     
   var  localData = "localData here" ;   
      document.addEventListener( 'click' ,    //處理點擊事件時用到了外部局部變量,好比這裏的localData      
         function (){             
            console.log(localData);
     });
}();

又好比下面這個例子:(是否是很親切~~)前端

1
2
3
4
5
6
7
8
9
10
11
! function () {     
   var  localData = "localData here" ;     
   var  url = "http://www.baidu.com/" ;     
   $.ajax({
      url : url,         
      success : function () {             
         // do sth...             
         console.log(localData);
         }
     });
}(); 

再來看一個例子~~這種狀況就是咱們一般所說的閉包java

1
2
3
4
5
6
7
8
function  outer() {  
   var  localVal = 30;   
   return  function (){     
     return  localVal;   
   }
}
var  func = outer(); 
func(); // 30

這個例子中調用outer()返回匿名函數function(),這個匿名函數中能夠訪問outer()的局部變量localVal,在outer()調用結束後,再次調用func()的時候,仍然能訪問到outer()的局部變量localValajax

閉包的概念

閉包,不一樣於通常的函數,它容許一個函數在當即詞法做用域外調用時,仍可訪問非本地變量。 --維基百科 編程

閉包就是可以讀取其餘函數內部變量的函數。 --阮一峯閉包

因爲在Javascript語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成"定義在一個函數內部的函數"。函數

因此,在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑post

閉包的用途

這部分轉自這篇博文性能

閉包能夠用在許多地方。它的最大用處有兩個,一個是前面提到的能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中。

1
2
3
4
5
6
7
8
9
10
11
12
function  f1(){
     var  n=999;
    nAdd= function (){n+=1}
     function  f2(){
      alert(n);
    }
     return  f2;
  }
   var  result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證實了,函數f1中的局部變量n一直保存在內存中,並無在f1調用後被自動清除。

爲何會這樣呢?緣由就在於f1是f2的父函數,而f2被賦給了一個全局變量,這致使f2始終在內存中,而f2的存在依賴於f1,所以f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。

這段代碼中另外一個值得注意的地方,就是"nAdd=function(){n+=1}"這一行,首先在nAdd前面沒有使用var關鍵字,所以nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個匿名函數自己也是一個閉包,因此nAdd至關因而一個setter,能夠在函數外部對函數內部的局部變量進行操做。

閉包-封裝 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
( function () {  
    var  _userId = 23492;  
    var  _typeId = 'item' ;   
    var  export = {};
     
    function  converter(userId) {         
      return  +userId;
    }
     export.getUserId = function () {        
        return  converter(_userId);    
    }
    export.getTypeId = function () {         
       return  _typeId;
    }        
    window.export = export;   //經過此方式輸出
}());
 
   export.getUserId(); // 23492
   export.getTypeId();  // item
   export._userId;    // undefined 
   export._typeId;    // undefined      
   export.converter; // undefined

利用閉包的特性能讓咱們封裝一些複雜的函數邏輯,在這個例子中調用export上的方法(getUserId,getTypeId)間接訪問函數裏私有變量,可是直接調用export._userId是無法拿到_userId的。這也是Node裏面經常使用到特性吧~

常見錯誤之循環閉包 

下面這個案例,咱們添加3個div,值分別爲aaa,bbb,ccc,咱們想實現的是點擊aaa輸出1,點擊bbb輸出2,點擊ccc輸出3

1
2
3
4
5
6
7
document.body.innerHTML = "<div id=div1>aaa</div>"  + "<div id=div2>bbb</div><div id=div3>ccc</div>"
for  ( var  i = 1; i < 4; i++) {     
   document.getElementById( 'div'  + i).        
     addEventListener( 'click' , function () {        
     alert(i); // all are 4!
     }); 
}

結果點擊aaa,bbb仍是ccc都是alert(4)~~

產生這樣的問題在於這個i的值在初始化完成的時候就已是4了

要達到咱們想要的點擊aaa輸出1,點擊bbb輸出2,點擊ccc輸出3,要用到閉包的技巧,在每次循環的時候,用當即執行的匿名函數把它包裝起來,這樣子作的話,每次alert(i)的值就取自閉包環境中的i,這個i來自每次循環的賦值i就能輸出1,2,3了

1
2
3
4
5
6
7
8
9
document.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){ //②再用這個參數i,到getElementById()中引用    
     document.getElementById( 'div'  + i).      
       addEventListener( 'click' , function () {        
       alert(i); // 1,2,3
      }); 
   }(i);  //①把遍歷的1,2,3的值傳到匿名函數裏面

思考題

若是你能理解下面兩段代碼的運行結果,應該就算理解閉包的運行機制了。(來自阮老師)這題目總結得真秒~~

代碼片斷一。

1
2
3
4
5
6
7
8
9
10
var  name = "The Window" ;
var  object = {
  name : "My Object" ,
  getNameFunc : function (){
     return  function (){
       return  this .name;
    };
  }
};
alert(object.getNameFunc()());

代碼片斷二。

1
2
3
4
5
6
7
8
9
10
11
var  name = "The Window" ;
var  object = {
  name : "My Object" ,
  getNameFunc : function (){
     var  that = this ;
     return  function (){
       return  that.name;
    };
  }
};
alert(object.getNameFunc()());
 
分類:  WWW技術
相關文章
相關標籤/搜索