閉包是JS中一個(gè)重要概念呐舔,很有用處币励,但不好理解。這里從what珊拼、why食呻、how三個(gè)方面來總結(jié)閉包知識(shí)。
1 WHAT (閉包是什么?)
閉包:函數(shù)(A)中的函數(shù)(B)仅胞,可以訪問外部函數(shù)(A)內(nèi)部的所有變量每辟。
2 WHY (為什么閉包能訪問函數(shù)變量?)
2.1 作用域鏈
要理解閉包原理干旧,需要先了解作用域鏈的細(xì)節(jié)渠欺。
function compare(value1,value2){
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
var result = compare(5,10);
上面代碼,創(chuàng)建compare()時(shí)椎眯,會(huì)創(chuàng)建包含全局變量對(duì)象(含this挠将、compare、result)的作用域鏈编整。
而第一次調(diào)用compare()時(shí)舔稀,會(huì)創(chuàng)建一個(gè)局部執(zhí)行環(huán)境。再創(chuàng)建compare()活動(dòng)對(duì)象(包含this掌测、arguments内贮、value1、value2)赏半,推入執(zhí)行環(huán)境作用域鏈前端贺归。故compare()活動(dòng)對(duì)象處于作用域鏈第一位(0),而全局變量對(duì)象處于作用域鏈的第二位(1)断箫。
通過作用域鏈拂酣,函數(shù)可以訪問局部變量和全局變量。函數(shù)執(zhí)行后仲义,局部活動(dòng)對(duì)象會(huì)被銷毀婶熬,內(nèi)存只保存全局變量對(duì)象。但閉包的情況又不一樣了埃撵。
2.2閉包的作用域鏈
function createComparisonFunction(propertyName){
return function(object1, object2){ //閉包
var value1 = object1[propertyName];//訪問外部函數(shù)的變量
var value2 = object2[propertyName];
if(value1 > value2){
return 1;
}else if(value1 < value2){
return -1;
}else{
return 0;
}
};
}
// 創(chuàng)建函數(shù)
var compareNames = createComparison("name");
// 調(diào)用函數(shù)
var result = compareNames({name:"Lillian"},{name:"Matthew"});
外部函數(shù)<code>createComparitionFunction()</code>的活動(dòng)對(duì)象會(huì)添加到閉包的作用域鏈中赵颅,這樣閉包就可以訪問函數(shù)中的變量。問題是暂刘,<code>createComparitionFunction()</code>執(zhí)行完畢后饺谬,閉包仍然引用著它的活動(dòng)對(duì)象,所以無法銷毀其活動(dòng)對(duì)象谣拣。
只有閉包銷毀募寨,才能銷毀函數(shù)的活動(dòng)對(duì)象,釋放內(nèi)存:
// 解除對(duì)函數(shù)(閉包)的引用森缠,釋放內(nèi)存
compareNames = null;
3 HOW (怎樣使用閉包拔鹰?)
閉包有很多用途,這里只列舉最常用的兩種贵涵。
3.1 實(shí)現(xiàn)私有作用域
JS沒有塊級(jí)作用域概念:
function outputNumbers(count){
for(var i = 0; i< count; i++){
alert(i);
}
alert(i); // 不會(huì)報(bào)錯(cuò)
}
由于沒有塊級(jí)作用域列肢,for循環(huán)結(jié)束后恰画,<code>i</code>并不會(huì)被銷毀。所以<code>alert(i)</code>不會(huì)報(bào)錯(cuò)瓷马。
使用自調(diào)用函數(shù)可以模仿塊級(jí)作用域:
(function(){
//這里是塊級(jí)作用域
})()
其實(shí)自調(diào)用函數(shù)實(shí)現(xiàn)私有作用域拴还,與閉包沒有必然聯(lián)系,只是自調(diào)用函數(shù)也可以用于函數(shù)內(nèi)部(作為閉包):
function outputNumbers(count){
(function(){
for(var i = 0; i< count; i++){
alert(i);
}
})()
alert(i); // 報(bào)錯(cuò)欧聘,i沒有定義
}
For循環(huán)放在自調(diào)用函數(shù)(此處是閉包)中自沧,這樣變量<code>i</code>只能在循環(huán)中被訪問,在循環(huán)外部無法訪問树瞭。
3.2 訪問私有變量
在函數(shù)中定義的變量(參數(shù)、局部變量爱谁、內(nèi)部函數(shù))晒喷,都不能在外部訪問,所以是私有變量访敌。而閉包可以訪問函數(shù)中的變量凉敲,這就提供了訪問私有變量的共有方法(特權(quán)方法)。
對(duì)于對(duì)象來說寺旺,有下面幾種方式訪問私有變量:
(1) 構(gòu)造函數(shù)模式:
function MyObject() {
//私有變量和私有函數(shù)
var privateVariable = 10;
function privateFunction(){
return false;
}
//特權(quán)方法
this.publicMethod = function() {
privateVariable++;
return privateFunction();
};
}
var obj1 = new MyObject();
console.log(obj1.publicMethod());
創(chuàng)建<code>MyObject</code>的實(shí)例<code>obj1</code> 后爷抓,只能用<code>publicMethod() </code>訪問<code>privateVariable</code>和<code>privateFunction()</code>,沒有其他方法可以直接訪問私有變量和私有函數(shù)阻塑。
(2)原型模式:
(function(){
//私有變量和私有函數(shù)
var privateVariable = 10;
function privateFunction() {
return false;
}
//構(gòu)造函數(shù)
MyObject = function(){
};
/*函數(shù)聲明只能創(chuàng)建局部函數(shù)蓝撇,所以這里使用了函數(shù)表達(dá)式。
注意:變量MyObject沒有加var陈莽,所以是全局變量渤昌。在私有作用域外部也能訪問。*/
//特權(quán)方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
}
})();
原型模式與構(gòu)造函數(shù)模式最主要的區(qū)別就是私有變量和私有函數(shù)由實(shí)例共享的走搁。這樣独柑,變量就成了靜態(tài)的、由所有實(shí)例共享的屬性私植,即靜態(tài)私有變量忌栅。
(3)模塊模式:
模塊模式用于為單例創(chuàng)建私有變量和特權(quán)方法。(單例:只有一個(gè)實(shí)例的對(duì)象曲稼。)
var singleton = function(){
//私有變量和私有函數(shù)
var privateVariable = 10;
function privateFunction(){
return false;
}
//公有方法和屬性
return {
publicProperty: true,
publicMethod: function(){
privateVariable++;
return privateFunction();
}
};
}();
該模式返回一個(gè)對(duì)象字面量索绪,包含公有屬性和方法。該對(duì)象是在匿名函數(shù)內(nèi)部定義的躯肌,所以它的公有方法可以訪問私有變量和函數(shù)者春。
(4)增強(qiáng)的模塊模式:
var singleton = function(){
//私有變量和私有函數(shù)
var privateVariable = 10;
function privateFunction(){
return false;
}
//創(chuàng)建對(duì)象,這里CustomType是一種實(shí)例類型清女,我們不需要理會(huì)它的具體代碼
var object = new CustomType();
//公有屬性和方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
}
//返回對(duì)象
return object;
}();
增強(qiáng)的模塊模式钱烟,不是直接返回對(duì)象字面量,而是創(chuàng)建一個(gè)對(duì)象實(shí)例,增加屬性和方法后返回拴袭。
這種模式读第,適合單例是某種類型實(shí)例的情況。上面的代碼拥刻,object是CustomType的實(shí)例怜瞒,匿名函數(shù)返回object對(duì)象,并賦值給singleton變量般哼。所以吴汪,該單例singleton是CustomType的實(shí)例。
代碼來源:
《JavaScript 高級(jí)程序設(shè)計(jì)》