1. 程式人生 > 其它 >全面理解Javascript閉包

全面理解Javascript閉包

技術標籤:web前端

什麼是閉包

閉包,官方對閉包的解釋是:一個擁有許多變數和綁定了這些變數的環境的表示式(通常是一個函式),因而這些變數也是該表示式的一部分。閉包的特點:

  1. 作為一個函式變數的一個引用,當函式返回時,其處於啟用狀態。
  2. 一個閉包就是當一個函式返回時,一個沒有釋放資源的棧區。

簡單的說,Javascript允許使用內部函式—即函式定義和函式表示式位於另一個函式的函式體內。而且,這些內部函式可以訪問它們所在的外部函式中宣告的所有區域性變數、引數和宣告的其他內部函式。當其中一個這樣的內部函式在包含它們的外部函式之外被呼叫時,就會形成閉包。

閉包的幾種寫法

  1. 給函式新增一些屬性
//第1種寫法  
function Circle(r) { this.r = r; } Circle.PI = 3.14159; Circle.prototype.area = function() { return Circle.PI * this.r * this.r; } var c = new Circle(1.0); alert(c.area());
  1. 將一個函式當作值賦給變數
//第2種寫法  
var Circle = function() {  
   var obj = new Object();  
   obj.PI = 3.14159
; obj.area = function( r ) { return this.PI * r * r; } return obj; } var c = new Circle(); alert( c.area( 1.0 ) );
  1. 這種方法最好理解,就是new 一個物件,然後給物件新增屬性和方法
//第3種寫法  
var Circle = new Object();  
Circle.PI = 3.14159;  
Circle.Area = function( r ) {  
       return this.PI * r *
r; } alert( Circle.Area( 1.0 ) );
  1. 這種方法使用較多,也最為方便。var obj = {}就是宣告一個空的物件
//第4種寫法  
var Circle={  
   "PI":3.14159,  
   "area":function(r){  
       return this.PI * r * r;  
    }  
};  
alert( Circle.area(1.0) );
  1. 這種寫法我是沒用過,也不便於理解,大家可以瞭解一下
//第5種寫法  
var Circle = new Function("this.PI = 3.14159;this.area = function( r ) {return r*r*this.PI;}");  
  
alert( (new Circle()).area(1.0) );  
總的來說,上面幾種方法,第2中和第4中較為常見,大家可以根據習慣選擇。

Javascript閉包的用途

  1. 匿名自執行函式
    我們知道所有的變數,如果不加上var關鍵字,則預設的會新增到全域性物件的屬性上去,這樣的臨時變數加入全域性物件有很多壞處,
    比如:別的函式可能誤用這些變數;造成全域性物件過於龐大,影響訪問速度(因為變數的取值是需要從原型鏈上遍歷的)。
    除了每次使用變數都是用var關鍵字外,我們在實際情況下經常遇到這樣一種情況,即有的函式只需要執行一次,其內部變數無需維護,
    比如UI的初始化,那麼我們可以使用閉包:
var data= {    
    table : [],    
    tree : {}    
};    
     
(function(dm){    
    for(var i = 0; i < dm.table.rows; i++){    
       var row = dm.table.rows[i];    
       for(var j = 0; j < row.cells; i++){    
           drawCell(i, j);    
       }    
    }    
       
})(data);
//ES5裡的var沒有塊狀作用域
//ES5裡只有函式才有作用域的概念,通過閉包解決i的作用域問題
for(var i=0;i<3;i++){
	(function(i){
		btn[i].addEventListener('click',function(){
			//處理邏輯
		})
	})(i);
}

//ES6裡的let具有塊狀作用域,每次迴圈的i都具有獨立的空間
//而這種寫法var是做不到的,var以最後一次迴圈i=2作為所有事件最終處理邏輯
for(let i=0;i<3;i++){
	btn[i].addEventListener('click',function(){
		//處理邏輯
	})
}

我們建立了一個匿名的函式,並立即執行它,由於外部無法引用它內部的變數,因此在函式執行完後會立刻釋放資源,關鍵是不汙染全域性物件。

  1. 結果快取
    開發中有時需要將計算出來的值儲存起來,當呼叫這個函式的時候,首先在快取中查詢,如果找不到,則進行計算,然後更新快取並返回值,如果找到了,直接返回查詢到的值即可。閉包正是可以做到這一點,因為它不會釋放外部的引用,從而函式內部的值可以得以保留
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");
  1. 封裝
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("abruzzi");    
print(person.getName());    
   
得到結果如下:  

undefined  
default  
abruzzi
var mymath = (function () {
	var counter = 0;
	return  {
		myadd:function(){
			return counter += 1;
		},
		mydeduct:function(){
			return counter -= 1;
		}
	}
})();
function add(){
	document.getElementById("demo").innerHTML = mymath.myadd();
}
function deduct(){
	document.getElementById("demo").innerHTML = mymath.mydeduct();
}
  1. 實現類和繼承
function Person(){    
    var name = "default";       
    return {    
       getName : function(){    
           return name;    
       },    
       setName : function(newName){    
           name = newName;    
       }    
    }    
};   

var p = new Person();
p.setName("Tom");
alert(p.getName());
var Jack = function(){};
//繼承自Person
Jack.prototype = new Person();
//新增私有方法
Jack.prototype.Say = function(){
    alert("Hello,my name is Jack");
};
var j = new Jack();
j.setName("Jack");
j.Say();
alert(j.getName());