閉 包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應(yīng)用都要依靠閉包實現(xiàn)。
下面就是我的學(xué)習(xí)筆記,對于Javascript初學(xué)者應(yīng)該是很有用的。
一、變量的作用域
要理解閉包,首先必須理解Javascript特殊的變量作用域。
變量的作用域無非就是兩種:全局變量和局部變量。
Javascript語言的特殊之處,就在于函數(shù)內(nèi)部可以直接讀取全局變量。
var n=999;
function f1(){
alert(n);
}f1(); // 999
另一方面,在函數(shù)外部自然無法讀取函數(shù)內(nèi)的局部變量。
function f1(){
var n=999;
}alert(n); // error
這里有一個地方需要注意,函數(shù)內(nèi)部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!
function f1(){
n=999;
}f1();
alert(n); // 999
二、如何從外部讀取局部變量?
出于種種原因,我們有時候需要得到函數(shù)內(nèi)的局部變量。但是,前面已經(jīng)說過了,正常情況下,這是辦不到的,只有通過變通方法才能實現(xiàn)。
那就是在函數(shù)的內(nèi)部,再定義一個函數(shù)。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}}
在上面的代碼中,函數(shù)f2就被包括在函數(shù)f1內(nèi)部,這時f1內(nèi)部的所有局部變量,對f2都是可見的。但是反過來就不行,f2內(nèi)部的局部變量,對f1就是不可見的。這就是Javascript語言特有的"鏈?zhǔn)阶饔糜?結(jié)構(gòu)(chain scope),子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。
既然f2可以讀取f1中的局部變量,那么只要把f2作為返回值,我們不就可以在f1外部讀取它的內(nèi)部變量了嗎!
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
三、閉包的概念
上一節(jié)代碼中的f2函數(shù),就是閉包。
各種專業(yè)文獻上的"閉包"(closure)定義非常抽象,很難看懂。我的理解是,閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
由于在Javascript語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡單理解成"定義在一個函數(shù)內(nèi)部的函數(shù)"。
所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。
四、閉包的用途
閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數(shù)內(nèi)部的變量,另一個就是讓這些變量的值始終保持在內(nè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實際上就是閉包f2函數(shù)。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數(shù)f1中的局部變量n一直保存在內(nèi)存中,并沒有在f1調(diào)用后被自動清除。
為什么會這樣呢?原因就在于f1是f2的父函數(shù),而f2被賦給了一個全局變量,這導(dǎo)致f2始終在內(nèi)存中,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中,不會在調(diào)用結(jié)束后,被垃圾回收機制(garbage collection)回收。
這段代碼中另一個值得注意的地方,就是"nAdd=function(){n+=1}"這一行,首先在nAdd前面沒有使用var關(guān)鍵字,因此nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(shù)(anonymous function),而這個匿名函數(shù)本身也是一個閉包,所以nAdd相當(dāng)于是一個setter,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進行操作。
五、使用閉包的注意點
1)由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
2)閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當(dāng)作對象(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。
六、思考題
如果你能理解下面兩段代碼的運行結(jié)果,應(yīng)該就算理解閉包的運行機制了。
代碼片段一。
var name = "The Window";
var object = {
name : "My Object",getNameFunc : function(){
return function(){
return this.name;
};}
};
alert(object.getNameFunc()());
代碼片段二。
var name = "The Window";
var object = {
name : "My Object",getNameFunc : function(){
var that = this;
return function(){
return that.name;
};}
};
alert(object.getNameFunc()());
(完)
<script src="/newwindow.js" type="text/javascript"></script>
相關(guān)文章
-
2011.08.11:
Java開源建站工具
美國程序員Jon Scott Stevens,公布了他的創(chuàng)業(yè)公司所使用的開發(fā)工具清單。
-
2011.08.09:
數(shù)字簽名是什么?
今天,我讀到一篇好文章。
功能鏈接
- 前一篇: Web service是什么?
- 后一篇: 紀(jì)錄片《Code Rush》
- 更多內(nèi)容請訪問: 首頁 ? 檔案 ? IT技術(shù)
- <!-- SiteSearch Google --> 站內(nèi)搜索: <!-- SiteSearch Google -->
-
Feed訂閱:
廣告 (購買廣告位)
留言(53條)
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

forex88 說:
講的很清楚明了,連我都懂了,要是我們大學(xué)時的老師也能這么講課。。。他們只會放幻燈片
2009年8月30日 22:29 | 檔案 | 引用
明城 說:
這里有個 PPT 用于說明 JS 閉包,說明得很透徹: http://www.gracecode.com/archives/2385/
2009年8月30日 22:44 | 檔案 | 引用
張昭 說:
呵呵,可以作為面試題了!
2009年8月31日 09:30 | 檔案 | 引用
十三 說:
閉包個人感覺是一種描述函數(shù)內(nèi)部的數(shù)據(jù)結(jié)構(gòu),來描述函數(shù)的運行上下文.Javascript編程精粹 這本書算是講的比較好一點.
2009年8月31日 09:40 | 檔案 | 引用
迷途小書童 說:
類是有行為的數(shù)據(jù),閉包是有數(shù)據(jù)的行為。
2009年8月31日 10:26 | 檔案 | 引用
tt 說:
阮兄:
有點疑問:
function f1(){
n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
可以寫成如下的不也一樣么?
function f1(){
n=999;
return n;
}
var result=f1();
alert(result);
2009年8月31日 21:16 | 檔案 | 引用
明城 說:
@tt 實際上后種方法每次調(diào)用 f1 時,都會聲明 n = 999,而且 n 無法保留狀態(tài)值(嚴(yán)格按照你的代碼,其實 n 為全局變量,我理解你的本意為 var n = 999;)。
而第一種 f1 實際上返回的是個匿名函數(shù),這樣 n 作用域被另外個 f2 函數(shù)作用域所使用,因此它會保留。n 不會被重復(fù)聲明,且內(nèi)容會被保存
2009年9月 1日 13:20 | 檔案 | 引用
SpiderMan 說:
感覺這里的例子更好一些 https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Working_with_Closures
2009年9月 1日 20:46 | 檔案 | 引用
ahwing 說:
這是我見過最簡單易懂的閉包教程。
支持下。
博主的博客寫的不錯,簡單易懂,東西涉及的很多方面我都有興趣,看來是同道中人,^_^
2009年9月 2日 16:49 | 檔案 | 引用
星光 說:
一文中的!!!!!!!!!!!!
學(xué)習(xí)了!!
2009年9月 3日 11:08 | 檔案 | 引用
zhaorui 說:
想知道思考題的答案,
我以為是:My Object
2009年9月15日 00:09 | 檔案 | 引用
steven 說:
頂樓主,我讀了一些文章。不是特明白。
有個問題。
記得有人說。外面的函數(shù)是closure,
好像樓主說里面的函數(shù)是closure.
不知道到底哪個是?謝謝。
2009年11月21日 14:51 | 檔案 | 引用
jkd___w 說:
樓主講講最后一個思考題,沒明白
2009年11月26日 09:21 | 檔案 | 引用
hou 說:
請版主講一講最后一個例子怎么回事,沒有看明白
2009年11月30日 16:00 | 檔案 | 引用
George Wing 說:
函數(shù)中的this一般是指向window中的變量。
2009年12月13日 09:55 | 檔案 | 引用
George Wing 說:
上面本人說得不太正確。
this的指向是由它所在函數(shù)調(diào)用的上下文決定的,而不是由它所在函數(shù)定義的上下文決定的。
2009年12月13日 10:23 | 檔案 | 引用
George Wing 說:
如果非要指向object,可顯式的控制--把代碼的最后一句改為 alert(object.getName().call(object));
2009年12月13日 11:39 | 檔案 | 引用
c-star 說:
阮大哥講的很透徹 受益匪淺
2009年12月18日 16:32 | 檔案 | 引用
ya 說:
大道至簡,給予我這個初學(xué)者很大的幫助,謝謝!
2010年1月11日 09:30 | 檔案 | 引用
過客 說:
淺顯易懂,很好。
如下看法,認(rèn)為有待商榷:
#1、有一個地方需要注意,函數(shù)內(nèi)部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!
#2、這段代碼中另一個值得注意的地方,就是“nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關(guān)鍵字,因此nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(shù)(anonymous function),而這個匿名函數(shù)本身也是一個閉包,所以nAdd相當(dāng)于是一個setter,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進行操作。
function f1(){
test = 10;
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
//如果 #1 說法正確,下句會打印10,實際結(jié)果是test未定義。
//alert(test); // error test 未定義
//如果 #2 正確,語句 nAdd(); 位置在何處應(yīng)該都能執(zhí)行,測試結(jié)果在下面這個位置,也就是語句 var result=f1(); 前。是不能執(zhí)行的。
//nAdd();
var result=f1();
result(); // 999
nAdd();
result(); // 1000
2010年1月28日 11:36 | 檔案 | 引用
ning 說:
To 過客:
函數(shù)內(nèi)部定義的方法和變量,要等到函數(shù)執(zhí)行過以后,才會真正定義
2010年3月20日 16:17 | 檔案 | 引用
Jason 說:
但是從過客說的里面可以引出另外的問題,當(dāng)使用這樣的代碼時。
然后調(diào)用 則顯示為999。說明nAdd中的n確實是作為全局變量存在。于是問題就來了——有什么方法讓他可以是父函數(shù)中定義的n呢?function f1(){
test = 10;
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
如果在函數(shù)f1定義之前添加變量定義
?
2010年4月26日 15:28 | 檔案 | 引用
西子湖畔的樹人 說:
大道至簡,很不錯!~ 這篇文章我要轉(zhuǎn)了...
2010年4月28日 22:48 | 檔案 | 引用
iworm 說:
this關(guān)鍵字代表的實例會根據(jù)環(huán)境不同而變化的. 他總是指向owner 看看這篇你大概就動this這個關(guān)鍵字了
http://www.quirksmode.org/js/this.html
2010年4月29日 12:34 | 檔案 | 引用
tomwang 說:
最后一個題感覺和閉包沒什么關(guān)系啊,能詳細(xì)解釋一下嗎?因為當(dāng)一個函數(shù)作為函數(shù)而不是方法來調(diào)用的時候,this指向的是全局對象,這在《Javascript權(quán)威指南》上說的很清楚,所以答案肯定是“The Window”,和閉包沒什么關(guān)系啊
2010年5月23日 18:24 | 檔案 | 引用
afity 說:
最后一題重點在this
2010年8月25日 23:26 | 檔案 | 引用
bao 說:
如果把f2申明成全局變量,道理一樣嗎?
2010年9月 6日 17:45 | 檔案 | 引用
小貓 說:
太經(jīng)典了!
終于理解了,一箭雙雕啊!既理解了this的用法,又理解了閉包
2010年9月16日 20:22 | 檔案 | 引用
soberlevi 說:
這個例子很不錯,真的是一箭雙雕
2010年10月15日 09:29 | 檔案 | 引用
小彘 說:
前面講得挺好的,淺顯易懂。對最后的兩個例子搞不清楚為啥。版主能不能具體分析下。
var obj=function()
{
var MyFunc=function()
{
alert("hello world");
}
return function()
{
return MyFunc;
}
}()
var f3=obj();
var f4=obj();
alert(f3 === f4);//為啥是TRUE;搞不懂
2010年10月24日 09:39 | 檔案 | 引用
hellowang 說:
最后兩個例子很精煉 ^ ^
2010年11月11日 15:24 | 檔案 | 引用
陳銳達 說:
嘗試解答代碼段一:
getNameFunc: function() {//假設(shè)函數(shù)名為A
return function()/*假設(shè)函數(shù)名為B*/ { return this.name; };
}
在函數(shù)里面構(gòu)建函數(shù)的時候,閉包產(chǎn)生。
在函數(shù)B內(nèi)調(diào)用函數(shù)A的this.name,由于函數(shù)A沒有name屬性,所以就去找全局變量name,找到了,所以返回“The Window”,要是沒有找到,則返回“undefined”。
代碼段二可以嘗試將代碼更改為:
var _this = this;
return function() { return _this.name +"__"+ this.name; };
2010年12月13日 15:50 | 檔案 | 引用
LuckyGeb 說:
只有一點沒弄懂,如下代碼,nAdd在函數(shù)外為什么可以有意義?而test不行?想了好久,不知道那里有解答
function f1(){
test=10;
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
2011年1月17日 01:17 | 檔案 | 引用
qdsang 說:
通俗易懂,, 閱覽無數(shù)教程, 看了這篇, 終于明白了點.
2011年1月24日 13:21 | 檔案 | 引用
sf 說:
寫得太好了
2011年2月15日 16:47 | 檔案 | 引用
Luke 說:
這篇文章是阮兄一貫的風(fēng)格,我喜歡,不過 "Javascript語言的特殊之處,就在于函數(shù)內(nèi)部可以直接讀取全局變量。"這句有點奇怪,c不一樣可以在函數(shù)內(nèi)部直接讀取全局變量么?難道不是么?
2011年3月 2日 22:02 | 檔案 | 引用
ignition 說:
阮大哥能不能具體講下最后的思考題啊? 感覺關(guān)鍵在this
2011年3月 8日 15:56 | 檔案 | 引用
三少爺 說:
變量的作用域無非就是兩種:全局變量和局部變量。
這句話值得商榷, 變量的作用域確實只有兩種, 不過他們是全局對象和函數(shù).
你想說的或許是變量的類型有兩種?
2011年3月25日 20:15 | 檔案 | 引用
軒脈刃 說:
理解最后兩個例子:
1 函數(shù)中的this指的是調(diào)用這個函數(shù)的owner
2 object.getNameFunc()是返回一個函數(shù),并沒有執(zhí)行函數(shù)中的代碼
3 增加一個例子0:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return (this.name);
}
};
var name = object.getNameFunc();
alert(name);
4 把例子1變成
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name; //這個this是有上下文的限制的
};
}
};
var tmp = Object.getNameFunc(); //此時沒有執(zhí)行this.name
var name = tmp();//這個時候才執(zhí)行,這時候的this上下文為全局
alert(name);
//alert(object.getNameFunc()())
5 把例子2變成:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
var tmp = Object.getNameFunc(); //這個時候執(zhí)行了that = this,這里的this上下文是object,所以that指的是object
var name = Object.getNameFunc(); //這個時候執(zhí)行了that.name
alert(name);
//alert(object.getNameFunc()());
2011年4月19日 15:28 | 檔案 | 引用
小秦 說:
你自己描述的是 f1()() 顯示的是999,說明 n 是使用的f1內(nèi)部的變量n,而非是全局變量n,不知道你為什么會有
這種想法呢? 如果想在nAdd中使用全局變量n(即在函數(shù)外面定義的n)的話,使用window.n來訪問.?
2011年4月20日 14:56 | 檔案 | 引用
小秦 說:
樓主文章中的:
這一整大段中的
根據(jù)整篇文章所表達的內(nèi)容,應(yīng)該為:
因為如果沒有加var,則聲明的是全局變量,既然是全局變量,則在所有函數(shù)內(nèi)部都是可見的,也就不會存在閉包這種說法.
?
請求樓主修正.
2011年4月20日 15:02 | 檔案 | 引用
小秦 說:
因為f3和f4都指向同一個地址(即MyFunc).
?
2011年4月20日 15:05 | 檔案 | 引用
小洪 說:
我測試了一下,為什么第一個例子輸出的什么都沒有是null,第二個我理解是myobject。誰能解釋下
2011年4月20日 16:48 | 檔案 | 引用
Ruan YiFeng 說:
謝謝指出,已更正。
2011年4月20日 16:53 | 檔案 | 引用
sitearth 說:
起初以為函數(shù)內(nèi)用var聲明變量,就等于用了this聲明,其實不是
var w=100;
function f1(){
//var w=101;
//this.w=102;
function f2(){
document.write(this.w);
}
return f2;
}
f1()();
輸出:100
var w=100;
function f1(){
var w=101;
//this.w=102;
function f2(){
document.write(this.w);
}
return f2;
}
f1()();
輸出:100
var w=100;
function f1(){
w=101;
//this.w=102;
function f2(){
document.write(this.w);
}
return f2;
}
f1()();
輸出:101
var w=100;
function f1(){
//var w=101;
this.w=102;
function f2(){
document.write(this.w);
}
return f2;
}
f1()();
輸出:102
看起來函數(shù)中的var和this并不是一個概念,函數(shù)內(nèi)的局部變量與函數(shù)的屬性不是一回事,不過通過上面的情況能夠加深理解this和閉包
2011年4月22日 11:27 | 檔案 | 引用
Aizen 說:
很不錯的講解,樓主寫的通俗易懂,很棒的理解,很受用!我的qq:290913917 希望有機會成為共同研究javascript和html5的伙伴,謝謝!
2011年5月 1日 00:10 | 檔案 | 引用
lily 說:
我感覺第一個思考題是不是這樣理解:
首先this指向的是當(dāng)前運行該函數(shù)的對象,
1、object.getNameFunc()得到了一個函數(shù),函數(shù)為function(){return this.name}
2、object.getNameFunc()(),此時為window運行該函數(shù),所以this指向的是window,所以this.name為The window
2011年5月 4日 11:29 | 檔案 | 引用
foxracle 說:
做習(xí)題之前有一點需要很清楚:
內(nèi)部函數(shù)可以訪問定義它們的外部函數(shù)的參數(shù)和變量(除了this和arguments之外)
如果需要訪問對象的name屬性的話,就需要顯示的定義一個變量that來引用this,而這個變量此時就指向object對象了。
第一題改成下面這樣就很清楚了。getNameFunc的第一個()是屬于方法調(diào)用,所以this綁定到了object對象,自然this.name為"My Object",但是閉包函數(shù)無法訪問這個this,它只能訪問到全局的this。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
alert(this.name);
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
2011年5月23日 15:44 | 檔案 | 引用
CODER 說:
寫的真不錯。。。看了很多文章講閉包都是云里霧里的。。看了本文才恍然大悟。。。哦原來閉包如此簡單。。。。樓主寫的不錯。。。
2011年6月 2日 15:56 | 檔案 | 引用
小超 說:
前面講的我都明白,但是最后兩個例子還是不明白,好多處都不懂!
1. var object = {。。。} 這是在干什么?是在聲明一個變量?還是在聲明一個類,然后里面有許多屬性?
2 . object.getNameFunc()(); 怎么會有兩個括號?
3. 如何判斷 this指向的是object 對象還是全局對象 ?
2011年6月14日 16:30 | 檔案 | 引用
anoymous 說:
閉包是運行時中的概念,不能講哪個函數(shù)是一個閉包!而是哪個函數(shù)在運行時存在一個閉包!有時候,好幾個函數(shù)都可以組成一個閉包呢:
function ff()
{
var local=1;
this.add1=function()
{
return ++local;
};
this.add2=function()
{
return ++local;
}
}
var f=new ff();
alert(f.add1());//2
alert(f.add2());//3
2011年6月24日 14:33 | 檔案 | 引用
Joe 說:
最后兩個例子中,第一個其實不是閉包,第二個是,但第二個例子其實不用那么復(fù)雜,直接把第一個例子中的this去掉就可以了。
2011年7月25日 14:02 | 檔案 | 引用
Revo 說:
為什么第一個運行以后結(jié)果是result?!既不是window也不是object....???
2011年8月12日 15:31 | 檔案 | 引用
?
?
?
轉(zhuǎn)自: http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html