大家好沿猜,我是IT修真院的學(xué)員碗脊,一枚正直純潔善良的web前端程序員。
今天給大家?guī)淼氖情]包是什么衙伶,用處如何?
1.背景介紹
閉包(closure)是JS中一個(gè)較難理解的一個(gè)概念矢劲,JS函數(shù)的執(zhí)行依賴于變量作用域,函數(shù)對象的內(nèi)部狀態(tài)包含函數(shù)自身的邏輯另绩,還必須引用當(dāng)前的作用域鏈。函數(shù)對象可以相互關(guān)聯(lián)起來花嘶,函數(shù)體內(nèi)部的變量可以保存在函數(shù)作用域內(nèi),具有這種特性的函數(shù)稱為閉包车海。從這個(gè)概念上講,所有的函數(shù)都是閉包侍芝。
2.知識剖析
閉包可以用在許多地方埋同。它的最大用處有兩個(gè):
1.可以讀取函數(shù)內(nèi)部的變量
2.讓這些變量的值始終保存在內(nèi)存中
讀取函數(shù)內(nèi)部的變量的例子:
/*使用閉包讀取函數(shù)內(nèi)部的變量*/
function f1(){
n = 999;
function f2(){
alert(n);
}
return f2;
}
var result = f1();
result(); //999
在上面的代碼中,函數(shù)f2就被包括在函數(shù)f1內(nèi)部凶赁,這時(shí)f1內(nèi)部的所有局部變量逆甜,對f2都是可見的。但是反過來就不行交煞,f2內(nèi)部的局部變量斟或,對f1就是不可見的素征。這就是Javascript語言特有的"鏈?zhǔn)阶饔糜?結(jié)構(gòu)(chain scope)萝挤,子對象會一級一級地向上尋找所有父對象的變量。所以怜珍,父對象的所有變量,對子對象都是可見的绘面,反之則不成立侈沪。既然f2可以讀取f1中的局部變量,那么只要把f2作為返回值亭罪,我們不就可以在f1外部讀取它的內(nèi)部變量了嗎?
變量的值始終保存在內(nèi)存的例子:
/*使用閉包讓函數(shù)內(nèi)部的變量儲存在內(nèi)存中*/
function f1(){
n = 999;
nAdd = function(){
n+=1;
};
function f2(){
alert(n);
}
return f2;
}
var result = f1();
result();//999
nAdd();
result();//1000
在這段代碼中,result實(shí)際上就是閉包f2函數(shù)情组。它一共運(yùn)行了兩次,第一次的值是999箩祥,第二次的值是1000。這證明了袍祖,函數(shù)f1中的局部變量n一直保存在內(nèi)存中,并沒有在f1調(diào)用后被自動(dòng)清除捐凭。因?yàn)閒1是f2的父函數(shù),而f2被賦給了一個(gè)全局變量茁肠,這導(dǎo)致f2始終在內(nèi)存中缩举,而f2的存在依賴于f1垦梆,因此f1也始終在內(nèi)存中匹颤,不會在調(diào)用結(jié)束后,被垃圾回收機(jī)制(garbage collection)回收惋嚎。這段代碼中另一個(gè)值得注意的地方站刑,就是"nAdd=function(){n+=1}"這一行,首先在nAdd前面沒有使用var關(guān)鍵字绞旅,因此nAdd是一個(gè)全局變量,而不是局部變量因悲。其次,nAdd的值是一個(gè)匿名函數(shù)(anonymous function)晃琳,而這個(gè)匿名函數(shù)本身也是一個(gè)閉包讯检,所以nAdd相當(dāng)于是一個(gè)setter,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進(jìn)行操作人灼。
3.常見問題
window.onload = function(){
var el = document.getElementById("id");
el.onclick = function(){
alert(el.id);
}
}
這段代碼會造成內(nèi)存泄漏顾翼,為什么投放?
4.解決方案
內(nèi)存泄漏的原因:執(zhí)行這段代碼的時(shí)候适贸,將匿名函數(shù)對象賦值給el的onclick屬性;然后匿名函數(shù)內(nèi)部又引用了el對象拜姿,存在循環(huán)引用,所以不能被垃圾回收機(jī)制回收误阻;
修改后:
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循環(huán)引用
el.onclick = function(){
alert(id);
}
el = null; // 將閉包引用的外部函數(shù)中活動(dòng)對象清除
}
5.編碼實(shí)戰(zhàn)
在函數(shù)執(zhí)行過程中晴埂,為讀取和寫入變量的值,就需要在作用域鏈中查找變量
function compare(value1, value2){
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
以上代碼先定義了compare()函數(shù)儒洛,然后又在全局作用域中調(diào)用了它。
6.擴(kuò)展思考
閉包有什么優(yōu)缺點(diǎn)琅锻,何時(shí)使用向胡?
7.參考文獻(xiàn)
參考一:阮一峰的網(wǎng)絡(luò)日志:學(xué)習(xí)Javascript閉包
參考二:知乎專欄:JS中的閉包是什么惊完?
參考三:segmentfault:JS進(jìn)階之閉包
8.更多討論
討論一:在閉包中的this指向問題?
this是一個(gè)關(guān)鍵字而不是變量小槐,每個(gè)函數(shù)調(diào)用都包含一個(gè)this值,如果閉包在外部函數(shù)里是無法訪問this的件豌,除非在外部函數(shù)將this轉(zhuǎn)存為一個(gè)變量
討論二:閉包會產(chǎn)生內(nèi)存泄漏的原因控嗜?
閉包保存在內(nèi)存里的是我們需要的變量茧彤,不屬于內(nèi)存泄漏疆栏。IE在我們使用完閉包之后,依然回收不了閉包里面引用的變量壁顶。
課后
Q1:簡述閉包是什么
A1:外部函數(shù)訪問函數(shù)內(nèi)部變量
Q2:怎么樣避免被回收
A2:將函數(shù)賦值給變量。
Q3:內(nèi)存泄漏的原因
A3:閉包保存在內(nèi)存里的是我們需要的變量,不屬于內(nèi)存泄漏富岳。IE在我們使用完閉包之后拯腮,依然回收不了閉包里面引用的變量动壤。