理解javascript中的閉包

閱讀目錄

創(chuàng)新互聯(lián)主要從事成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)新巴爾虎左,10多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18980820575

  • 什么是閉包?
  • 閉包的特性
  • 閉包的作用:
  • 閉包的代碼示例
  • 注意事項(xiàng)
  • 總結(jié)

閉包在javascript來說是比較重要的概念,平時(shí)工作中也是用的比較多的一項(xiàng)技術(shù)。下來對(duì)其進(jìn)行一個(gè)小小的總結(jié)

什么是閉包?

官方說法:

閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包的常見方式,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù),通過另一個(gè)函數(shù)訪問這個(gè)函數(shù)的局部變量------《javascript高級(jí)程序設(shè)計(jì)第三版》

下面就是一個(gè)簡(jiǎn)單的閉包:

function A(){
 var text="hello world";
 function B(){
 console.log(text);
 }
 return B;
}
var c=A();
c(); // hello world 

按照字面量的意思是:函數(shù)B有權(quán)訪問函數(shù)A作用域中的變量(text),通過另一個(gè)函數(shù)C來訪問這個(gè)函數(shù)的局部變量text。因此函數(shù)B形成了一個(gè)閉包。也可以說C是一個(gè)閉包,因?yàn)镃執(zhí)行的實(shí)際是函數(shù)B。

這個(gè)需要注意的是,直接執(zhí)行A();是沒有任何反應(yīng)的。因?yàn)閞eturn B沒有執(zhí)行,除非是return B();

閉包的特性

閉包有三個(gè)特性:

 1.函數(shù)嵌套函數(shù)

 2.函數(shù)內(nèi)部可以引用外部的參數(shù)和變量

 3.參數(shù)和變量不會(huì)被垃圾回收機(jī)制回收

解釋一下第3點(diǎn),為什么閉包的參數(shù)和變量不會(huì)被垃圾回收機(jī)制回收呢?

首先我們先了解一下javascript的垃圾回收原理:

(1)、在javascript中,如果一個(gè)對(duì)象不再被引用,那么這個(gè)對(duì)象就會(huì)被GC(garbage collection)回收;

(2)、如果兩個(gè)對(duì)象互相引用,而不再被第3者所引用,那么這兩個(gè)互相引用的對(duì)象也會(huì)被回收。

上面的示例代碼中A是B的父函數(shù),而B被賦給了一個(gè)全局變量C(全局變量的生命周期直至瀏覽器卸載頁面才會(huì)結(jié)束),這導(dǎo)致B始終在內(nèi)存中,而B的存在依賴于A,因此A也始終在內(nèi)存中,不會(huì)在調(diào)用結(jié)束后,被垃圾回收機(jī)制(garbage collection)回收。

閉包的作用:

其實(shí)閉包的作用也是有閉包的特性決定的,根據(jù)上面的閉包特性,閉包的作用如下:

  1、可以讀取函數(shù)內(nèi)部的變量,而不是定義一起全局變量,避免污染環(huán)境

  2、讓這些變量的值始終保持在內(nèi)存中。

閉包的代碼示例

下面主要介紹幾種常見的閉包,并進(jìn)行解析:

demo1 局部變量的累加。

function countFn(){
 var count=1;
 return function(){  //函數(shù)嵌套函數(shù)
 count++;
 console.log(count);
 }
}
var y = countFn(); //外部函數(shù)賦給變量y;
y(); //2 //y函數(shù)調(diào)用一次,結(jié)果為2,相當(dāng)于countFn()()
y(); //3 //y函數(shù)調(diào)用第二次,結(jié)果為3,因?yàn)樯弦淮握{(diào)用的count還保存在內(nèi)存中,沒有被銷毀,所以實(shí)現(xiàn)了累加
y=null; //垃圾回收,釋放內(nèi)存
y(); // y is not a function

由于第一次執(zhí)行完,變量count還保存在內(nèi)存中,所以不會(huì)被回收,以致于第二次執(zhí)行的時(shí)候可以對(duì)上次的值就行累加。當(dāng)引入y=null時(shí),銷毀引用,釋放內(nèi)存

demo2 循環(huán)中使用閉包

代碼如下(下面的三個(gè)代碼示例):我們的目的是想在每次循環(huán)中調(diào)用循環(huán)序號(hào):

demo2-1

for (var i = 0; i < 10; i++) {
 var a = function(){
 console.log(i)
 }
 a() //依次為0--9
}

這個(gè)例子的結(jié)果是沒有題的,我們依次打印出了0-9

每一層匿名函數(shù)和變量i都組成了一個(gè)閉包,但是這樣在循環(huán)中并沒有問題,因?yàn)楹瘮?shù)在循環(huán)體中立即被執(zhí)行了

demo2-2

但是在setTimeout中就不一樣了

for(var i = 0; i < 10; i++) {
 setTimeout(function() {
 console.log(i); //10次10
 }, 1000);
}

我們期望的依次是打印出0--10,實(shí)際情況是打印出 10次10。即使吧setTimeout的時(shí)間改為0,也是打印出10個(gè)10。這是為什么呢?

這是因?yàn)閟etTimeout的一種機(jī)制,setTimeout是從任務(wù)隊(duì)列結(jié)束的時(shí)候開始計(jì)時(shí)的,如果前面有進(jìn)程沒有結(jié)束,那么它就等到它結(jié)束再開始計(jì)時(shí)。在這里,任務(wù)隊(duì)列就是它自己所在的循環(huán)。

循環(huán)結(jié)束setTimeout才開始計(jì)時(shí),所以無論如何,setTimeout里面的i都是最后一次循環(huán)的 i。該代碼中,最后的 i 為10,所以打印出了10個(gè)10.

這也就是為什么setTimeout的回調(diào)不是每次取循環(huán)時(shí)的值,而取最后一次的值

demo2-3

解決上面的setTimeout不能依次打印出循環(huán)的問題

for(var i=0;i<10;i++){
 var a=function(e){
 return function(){
  console.log(e); //依次輸入0--9
 }
 }
 setTimeout(a(i),0);
}

因?yàn)閟etTimeout第一個(gè)參數(shù)需要一個(gè)函數(shù),所以返回一個(gè)函數(shù)給它,返回的同時(shí)把 i 作為參數(shù)傳進(jìn)去,通過形參 e 緩存了i,也就是說e變量相當(dāng)于是 i 的一個(gè)拷貝 ,并帶進(jìn)返回的函數(shù)里面。

當(dāng) setTimeout 的執(zhí)行時(shí),它就擁有了對(duì) e 的引用,而這個(gè)值是不會(huì)被循環(huán)改變的。

也可以用下面的寫法,和上面類似:

for(var i = 0; i < 10; i++) {
 (function(e) {
 setTimeout(function() {
  console.log(e); //依次打印出0-9
 }, 0);
 })(i);
}

demo3 循環(huán)中添加事件

看下面的一個(gè)典型的demo.

我們希望每次點(diǎn)擊li的時(shí)候,alert出li的索引值,所以用下面的代碼:

<ul id="test">
 <li>第一個(gè)</li>
 <li>第二個(gè)</li>
 <li>第三個(gè)</li>
 <li>第四個(gè)</li>
</ul>
var nodes = document.getElementsByTagName("li");
for(i = 0,len=nodes.length;i<len;i++){
 nodes[i].onclick = function(){
 alert(i); //值全是4
 };
}

事與愿違,無論點(diǎn)擊哪一個(gè)li,都是alert(4),也就是都是alert循環(huán)結(jié)束之后的索引值。這是為什么呢?

這是因?yàn)檠h(huán)中為不同的元素綁定事件,事件回調(diào)函數(shù)里如果調(diào)用了跟循環(huán)相關(guān)的變量,則這個(gè)變量取循環(huán)的最后一個(gè)值。

由于綁定的回調(diào)函數(shù)是一個(gè)匿名函數(shù),所以上面的代碼中, 這個(gè)匿名函數(shù)是一個(gè)閉包,攜帶的作用域?yàn)橥鈱幼饔糜颍ㄒ簿褪莊or里面的作用域),當(dāng)事件觸發(fā)的時(shí)候,作用域中的變量已經(jīng)隨著循環(huán)走到最后了。

還有一點(diǎn)就是,事件是需要觸發(fā)的,而絕大多數(shù)情況下,觸發(fā)的時(shí)候循環(huán)已經(jīng)結(jié)束了,所以循環(huán)相關(guān)的變量就是最后一次的取值。

要實(shí)現(xiàn)點(diǎn)擊li,alert出li的索引值,需要將上面的代碼進(jìn)行以下的修改:

<ul id="test">
 <li>第一個(gè)</li>
 <li>第二個(gè)</li>
 <li>第三個(gè)</li>
 <li>第四個(gè)</li>
</ul>
var nodes=document.getElementsByTagName("li");
for(var i=0;i<nodes.length;i++){
 (function(e){
 nodes[i].onclick=function(){
  alert(e);
 };
 })(i)
}

解決思路: 增加若干個(gè)對(duì)應(yīng)的閉包域空間(這里采用的是匿名函數(shù)),專門用來存儲(chǔ)原先需要引用的內(nèi)容(下標(biāo))。

當(dāng)立即執(zhí)行函數(shù)執(zhí)行的時(shí)候,e 值不會(huì)被銷毀,因?yàn)樗睦锩嬗袀€(gè)匿名函數(shù)(也可以說是因?yàn)殚]包的存在,所以變量不會(huì)被銷毀)。執(zhí)行后,e 值 與全局變量 i 的聯(lián)系就切斷了,

也就是說,執(zhí)行的時(shí)候,傳進(jìn)的 i 是多少,立即執(zhí)行函數(shù)的 e 就是多少,但是 e 值不會(huì)消失,因?yàn)槟涿瘮?shù)的存在。

也可以用下面的解法,原理是一樣的:

<ul id="test">
 <li>第一個(gè)</li>
 <li>第二個(gè)</li>
 <li>第三個(gè)</li>
 <li>第四個(gè)</li>
</ul>
var nodes=document.getElementsByTagName('li');
for(var i = 0; i<nodes.length;i++){
 (function(){
 var temp = i;
 nodes[i].onclick = function () {
  alert(temp);
 }
 })();
}

注意事項(xiàng)

1、造成內(nèi)存泄露

由于閉包會(huì)攜帶包含它的函數(shù)的作用域,因此會(huì)比其他函數(shù)占用更多的內(nèi)存。過度使用閉包可能會(huì)導(dǎo)致內(nèi)存占用過多,所以只有在絕對(duì)必要時(shí)再考慮使用閉包。

2、在閉包中使用this也可能會(huì)導(dǎo)致一些問題。

代碼示例:來源于《js高級(jí)程序設(shè)計(jì)3》;

其實(shí)我們的目的是想alert出object里面的name

var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  return function(){
  return this.name;
  }
 }
 }
 alert(object.getNameFunc()()); // The Window

因?yàn)樵谌趾瘮?shù)中,this等于window,而當(dāng)函數(shù)被作為某個(gè)對(duì)象的方法調(diào)用時(shí),this等于那個(gè)對(duì)象。不過,匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此其this對(duì)象通常指向window。

每個(gè)函數(shù)在被調(diào)用時(shí),都會(huì)自動(dòng)取的兩個(gè)特殊變量:this和 arguments。內(nèi)部函數(shù)在搜索這兩個(gè)變量時(shí),只會(huì)搜索到其活動(dòng)對(duì)象為止。也就是說,里面的return function只會(huì)搜索

到全局的this就停止繼續(xù)搜索了。因?yàn)樗肋h(yuǎn)不可能直接訪問外部函數(shù)中的這兩個(gè)變量。

稍作修改,把外部作用域中的this對(duì)象保存在一個(gè)閉包能夠訪問的變量里。這樣就可以讓閉包訪問該對(duì)象了。

var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  var that=this;
  return function(){
  return that.name;
  }
 }
 }
 alert(object.getNameFunc()()); // My Object

我們把this對(duì)象賦值給了that變量。定義了閉包之后閉包也可以訪問這個(gè)變量。因此,即使在函數(shù)返回之后,that也仍引用這object,所以調(diào)用object.getNameFunc()()就返回 “My Object”了。

總結(jié)

當(dāng)在函數(shù)內(nèi)部定義了其他函數(shù),就創(chuàng)建了閉包。閉包有權(quán)訪問包含函數(shù)內(nèi)部的所有變量。

閉包的作用域包含著它自己的作用域、包含函數(shù)的作用域和全局作用域。

當(dāng)函數(shù)返回一個(gè)閉包時(shí),這個(gè)函數(shù)的作用域會(huì)一直在內(nèi)存中保存到閉包不存在為止。

使用閉包必須維護(hù)額外的作用域,所有過度使用它們可能會(huì)占用大量的內(nèi)存

以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持創(chuàng)新互聯(lián)!

網(wǎng)頁題目:理解javascript中的閉包
新聞來源:http://bm7419.com/article34/igsepe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、關(guān)鍵詞優(yōu)化、商城網(wǎng)站、網(wǎng)站制作App開發(fā)、網(wǎng)站維護(hù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作