JavaScript閉包原理與用法實(shí)例分析
本文實(shí)例講述了JavaScript閉包原理與用法。分享給大家供大家參考,具體如下:
1、與閉包有關(guān)的兩個(gè)概念:
1) 變量的作用域
不帶有關(guān)鍵字var的變量會(huì)成為全局變量;
在函數(shù)中使用關(guān)鍵字var聲明的變量是局部變量。
局部變量只有在函數(shù)內(nèi)部才能訪問(wèn)到,在函數(shù)外面是訪問(wèn)不到的。但在函數(shù)內(nèi)部可以通過(guò)作用域鏈一直向上搜索直到全局對(duì)象,也就是說(shuō),函數(shù)內(nèi)部可以訪問(wèn)函數(shù)外部的變量。
2) 變量的生存周期
對(duì)于全局變量,其生存周期是永久的,除非主動(dòng)銷毀這個(gè)全局變量;
而對(duì)于在函數(shù)內(nèi)用關(guān)鍵字var聲明的局部變量,當(dāng)退出函數(shù)時(shí),這些局部變量會(huì)隨著函數(shù)調(diào)用結(jié)束而被銷毀。
var func = function() {
var i = 1;
alert(i); // 輸出:1
};
alert(i); // 報(bào)錯(cuò):i is not defind.
例外情況:閉包
var func = function() {
var i = 1;
return function() {
alert(i);
i++;
}
};
var f1 = func();
f1(); // 輸出:1
f1(); // 輸出:2
var f2 = func();
f2(); // 輸出:1
f2(); // 輸出:2
2、從閉包的一個(gè)經(jīng)典應(yīng)用談起
<div>0</div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<script>
var divs = document.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++) {
divs[i].onclick = function() {
alert(i);
};
}
</script>
問(wèn)題:無(wú)論單擊哪個(gè)div,都會(huì)彈出5。
原因:onclick事件是異步觸發(fā)的,當(dāng)事件被觸發(fā)時(shí),for循環(huán)早已結(jié)束,此時(shí)變量i的值早已經(jīng)是5。
解決:在閉包的幫助下,把每次循環(huán)的i值都封閉起來(lái)。當(dāng)事件函數(shù)順著作用域鏈從內(nèi)到外查找變量i時(shí),會(huì)先找到被封閉在閉包環(huán)境的i,單擊div時(shí),會(huì)分別輸出0,1,2,3,4。
<div>0</div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<script>
var divs = document.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++) {
divs[i].onclick = (function(num) {
return function() {
alert(num);
};
})(i);
}
</script>
類似實(shí)例:閉包直接賦給數(shù)組
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
for (var i = 0; i < 10; i++)
alert(createFunctions()[i]());
結(jié)果:result的每個(gè)元素都返回10。
說(shuō)明:閉包的作用域鏈有明顯的副作用——閉包總是獲得外部函數(shù)變量的最終值。上面代碼中,外部函數(shù)產(chǎn)生一個(gè)函數(shù)數(shù)組result并返回。函數(shù)數(shù)組中的每個(gè)元素都是一個(gè)函數(shù),每個(gè)函數(shù)都返回 i變量。似乎每個(gè)函數(shù)應(yīng)該返回每次循環(huán)的i值,即依次返回0到9,但事實(shí)是,每個(gè)函數(shù)的返回結(jié)果都是10。這是因?yàn)槊總€(gè)內(nèi)部函數(shù)返回的是變量i,而不是i在某個(gè)時(shí)刻的特定值,而i的作用域是整個(gè)外部函數(shù),當(dāng)外部函數(shù)執(zhí)行完成后,i的值是10。
解決:在每個(gè)內(nèi)部函數(shù)的內(nèi)部,再產(chǎn)生一個(gè)匿名函數(shù)并返回。
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = (function(num) {
return function() {
return num;
};
})(i);
}
return result;
}
for (var i = 0; i < 10; i++)
alert(createFunctions()[i]());
結(jié)果:result依次返回0到9。
說(shuō)明:(i)使得該層匿名函數(shù)立即執(zhí)行。
3、閉包
有時(shí)候需要得到函數(shù)內(nèi)的局部變量。如何從外部讀取局部變量?那就是在函數(shù)的內(nèi)部,再定義一個(gè)函數(shù)。
閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中變量的函數(shù),創(chuàng)建閉包的最常見(jiàn)的方式就是在一個(gè)函數(shù)內(nèi)創(chuàng)建另一個(gè)函數(shù),通過(guò)另一個(gè)函數(shù)訪問(wèn)這個(gè)函數(shù)的局部變量,利用閉包可以突破作用鏈域,將函數(shù)內(nèi)部的變量和方法傳遞到外部。
① 閉包的原理
1) 后臺(tái)執(zhí)行環(huán)境中,閉包的作用域鏈包含著自己的作用域、函數(shù)的作用域和全局作用域。
2) 通常,函數(shù)的作用域和變量會(huì)在函數(shù)執(zhí)行結(jié)束后銷毀。
3) 但是,當(dāng)函數(shù)返回一個(gè)閉包時(shí),這個(gè)函數(shù)的作用域?qū)?huì)一直在內(nèi)存中保存到閉包不存在為止。
② 閉包的特性
1) 函數(shù)內(nèi)再嵌套函數(shù)。
2) 內(nèi)部函數(shù)可以引用外層的參數(shù)和變量。
3) 參數(shù)和變量不會(huì)被垃圾回收機(jī)制回收。
③ 閉包的用途
1) 讀取函數(shù)內(nèi)部的變量。
function f1(){
var n = 999;
function f2(){
alert(n);//999
}
}
在上面的代碼中,函數(shù)f2就被包括在函數(shù)f1內(nèi)部,這時(shí)f1內(nèi)部的所有局部變量,對(duì)f2都是可見(jiàn)的。但是反過(guò)來(lái)就不行,f2內(nèi)部的局部變量,對(duì)f1就是不可見(jiàn)的。既然f2可以讀取f1中的局部變量,那么只要把f2作為返回值,就可以在f1外部讀取它的內(nèi)部變量了。
function f1(){
var n = 999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result();//999
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。由于在Javascript語(yǔ)言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡(jiǎn)單理解成"定義在一個(gè)函數(shù)內(nèi)部的函數(shù)"。所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。
function f1(){
var 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)存中,并沒(méi)有在f1調(diào)用后被自動(dòng)清除。原因就在于f1是f2的父函數(shù),而f2被賦給了一個(gè)全局變量,這導(dǎo)致f2始終在內(nèi)存中,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中,不會(huì)在調(diào)用結(jié)束后,被垃圾回收機(jī)制回收。
2) 讓函數(shù)內(nèi)部的變量的值始終保持在內(nèi)存中(延長(zhǎng)局部變量的壽命)。
var print, add, set;
function closure() {
var number = 8;
print = function() {
alert(number);
}
add = function() {
number++;
}
set = function(x) {
number = x;
}
}
closure();//創(chuàng)建一個(gè)閉包
add();
print();//9
set(0);
print();//0
var oldClosure = print;
closure();//創(chuàng)建一個(gè)新的閉包
print();//8
oldClosure();//0
使用閉包的注意點(diǎn):由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會(huì)造成網(wǎng)頁(yè)的性能問(wèn)題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。也就是說(shuō),閉包會(huì)引用外部函數(shù)作用域,會(huì)占用更多的內(nèi)存,過(guò)度使用閉包,會(huì)導(dǎo)致性能問(wèn)題。所以,僅當(dāng)必要時(shí)才使用閉包。對(duì)產(chǎn)生閉包的函數(shù),使用后應(yīng)該解除引用。
3)自執(zhí)行函數(shù)+閉包減少全局變量污染(封裝私有變量)
var person = (function() {
var_name = "Alice";
var _id = 16;
return {
getUserInfo: function() {
return _name + ": " + _id;
}
}
})();
使用下劃線來(lái)約定私有變量_name和_age,它們被封裝在閉包產(chǎn)生的作用域中,外部是訪問(wèn)不到這兩個(gè)變量的,這就避免了對(duì)全局的命令污染。
④ 閉包的缺點(diǎn):
1) 需要維護(hù)額外的作用域。
2) 過(guò)渡使用閉包會(huì)占用大量?jī)?nèi)存。
4、this對(duì)象
在閉包內(nèi)使用this對(duì)象將產(chǎn)生一些復(fù)雜的行為。this對(duì)象的值基于函數(shù)所在的執(zhí)行環(huán)境在運(yùn)行時(shí)決定:在全局函數(shù)中使用時(shí),this等于window(非嚴(yán)格模式)或undefined(嚴(yán)格模式);而當(dāng)作為對(duì)象的方法調(diào)用時(shí),this等于這個(gè)對(duì)象。
var name = "The window";
var object = {
name: "My object",
getName: function() {
return function() {
return this.name;
};
}
};
alert(object.getName()());//輸出:"The window"
每個(gè)函數(shù)一旦被調(diào)用,它將自動(dòng)獲得this和arguments兩個(gè)變量。一個(gè)內(nèi)部函數(shù)是不能直接從外部函數(shù)訪問(wèn)到這兩個(gè)變量的。可以通過(guò)將this對(duì)象存儲(chǔ)在另一個(gè)變量中來(lái)解決這個(gè)問(wèn)題。把外部作用域中的this對(duì)象保存在一個(gè)閉包能夠訪問(wèn)到的變量里,就可以讓閉包訪問(wèn)該對(duì)象了。
var name ="The window";
var object = {
name: "My object",
getName: function() {
var that = this;
return function() {
return that.name;
};
}
};
alert(object.getName()());//輸出:"My object"
要讓閉包訪問(wèn)外部函數(shù)的this和arguments對(duì)象,可以通過(guò)將它們的引用存儲(chǔ)在另一個(gè)變量中來(lái)完成。
5、內(nèi)存泄漏
使用閉包的時(shí)候很容易造成循環(huán)引用,若閉包的作用域包含著一些DOM節(jié)點(diǎn),這時(shí)候就有可能造成內(nèi)存泄漏,但其實(shí),這本身不是閉包的問(wèn)題,而是由于:BOM和DOM中的對(duì)象是使用C++以COM對(duì)象的方式實(shí)現(xiàn)的,而COM對(duì)象的垃圾收集機(jī)制采用的是引用計(jì)數(shù)策略,在基于引用計(jì)數(shù)策略的垃圾回收機(jī)制中,若兩個(gè)對(duì)象之間形成了循環(huán)引用,則這兩個(gè)對(duì)象都無(wú)法被回收。
function assignHandler() {
var element = document.getElementById("id");
element.onclick = function() {
alert(element.id);
}
}
匿名函數(shù)保存了一個(gè)對(duì)element的引用,只要匿名函數(shù)存在,element的引用數(shù)至少為1,它所占用的內(nèi)存就永遠(yuǎn)不會(huì)被回收。
function assignHandler() {
var element = document.getElementById("id");
var id = element.id;
element.onclick = function() {
alert(id);
}
element = null;
}
通過(guò)把element.id的一個(gè)副本保存在變量中,并且在閉包中引用該變量消除了循環(huán)引用,但是僅僅做到這一步還不能解決內(nèi)存泄漏的問(wèn)題,閉包會(huì)引用包含函數(shù)的所有活動(dòng)對(duì)象,包含element,即使閉包不直接引用element,包含函數(shù)的活動(dòng)對(duì)象中也仍然會(huì)保存一個(gè)引用,因此有必要把element元素設(shè)置為null,這樣就能解除對(duì)DOM對(duì)象的引用,確保正常回收其占用的內(nèi)存。
6、模仿塊級(jí)作用域
JavaScript中沒(méi)有直接的塊級(jí)作用域。
function output(count) {
for (var i = 0; i < count; i++) {
alert(i);
}
alert(i);//輸出:10
}
使用閉包可以模仿塊級(jí)作用域——?jiǎng)?chuàng)建并立即調(diào)用一個(gè)函數(shù),這樣既可以執(zhí)行其中的代碼,又不會(huì)在內(nèi)存中留下對(duì)該函數(shù)的引用。結(jié)果是內(nèi)部函數(shù)的所有變量都會(huì)立即被銷毀,除非將某些變量賦值給了包含作用域(即外部作用域)中的變量。
用作塊級(jí)作用域的匿名函數(shù):將函數(shù)聲明包含在一對(duì)圓括號(hào)中,表示它實(shí)際上是一個(gè)函數(shù)表達(dá)式,緊隨其后的另一對(duì)圓括號(hào)會(huì)立即調(diào)用這個(gè)函數(shù)。
(function() {
//這里是塊級(jí)作用域;
}) ();
可以使用匿名函數(shù)表達(dá)式來(lái)模擬塊級(jí)作用域,任何定義在匿名函數(shù)中的變量在匿名函數(shù)執(zhí)行完之后都將被銷毀,在匿名函數(shù)外訪問(wèn)這些變量將會(huì)產(chǎn)生錯(cuò)誤。
function output(count) {
(function() {
for (var i = 0; i < count; i++) {
alert(i);
}
}) ();
alert(i);//出錯(cuò)
}
更多關(guān)于JavaScript相關(guān)內(nèi)容可查看本站專題:《javascript面向?qū)ο笕腴T(mén)教程》、《JavaScript常用函數(shù)技巧匯總》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
相關(guān)文章
這篇文章主要介紹了JS實(shí)現(xiàn)左邊列表移到到右邊列表功能,實(shí)現(xiàn)功能主要是左邊的下拉框內(nèi)容添加到右邊的下拉框,支持多選移動(dòng),且同時(shí)將右邊的下拉框?qū)ο笠瞥?,需要的朋友可以參考?/div> 2018-03-03
微信小程序清空輸入框信息與實(shí)現(xiàn)屏幕往上滾動(dòng)的示例代碼
這篇文章主要介紹了微信小程序清空輸入框信息與實(shí)現(xiàn)屏幕往上滾動(dòng)的示例代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
關(guān)于Promise基本方法的簡(jiǎn)單實(shí)現(xiàn)
Promise大家一定都不陌生了,JavaScript異步流程從最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await,下面這篇文章主要給大家介紹了關(guān)于Promise基本方法的簡(jiǎn)單實(shí)現(xiàn),需要的朋友可以參考下2022-02-02
基于JavaScript實(shí)現(xiàn)根據(jù)手機(jī)定位獲取當(dāng)前具體位置(X省X市X縣X街道X號(hào))
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)根據(jù)手機(jī)定位獲取當(dāng)前具體位置(X省X市X縣X街道X號(hào))的相關(guān)資料,需要的朋友可以參考下2015-12-12
用jmSlip編寫(xiě)移動(dòng)端頂部日歷選擇控件
這篇文章主要為大家詳細(xì)介紹了利用jmSlip編寫(xiě)移動(dòng)端頂部日歷選擇組件的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
分享19個(gè)JavaScript 有用的簡(jiǎn)寫(xiě)寫(xiě)法
最近很火的一篇來(lái)自國(guó)外的文章,js的簡(jiǎn)寫(xiě)寫(xiě)法一定程度上可以提高你的js書(shū)寫(xiě)水平對(duì)于js的理解也會(huì)更近一步2017-07-07
Egg.js 中 AJax 上傳文件獲取參數(shù)的方法
這篇文章主要介紹了Egg.js 中 AJax 上傳文件獲取參數(shù),需要的朋友可以參考下2018-10-10最新評(píng)論

