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

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

forex88 說:
講的很清楚明了,連我都懂了,要是我們大學時的老師也能這么講課。。。他們只會放幻燈片
2009年8月30日 22:29 | 檔案 | 引用
明城 說:
這里有個 PPT 用于說明 JS 閉包,說明得很透徹: http://www.gracecode.com/archives/2385/
2009年8月30日 22:44 | 檔案 | 引用
張昭 說:
呵呵,可以作為面試題了!
2009年8月31日 09:30 | 檔案 | 引用
十三 說:
閉包個人感覺是一種描述函數內部的數據結構,來描述函數的運行上下文.Javascript編程精粹 這本書算是講的比較好一點.
2009年8月31日 09:40 | 檔案 | 引用
迷途小書童 說:
類是有行為的數據,閉包是有數據的行為。
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 實際上后種方法每次調用 f1 時,都會聲明 n = 999,而且 n 無法保留狀態值(嚴格按照你的代碼,其實 n 為全局變量,我理解你的本意為 var n = 999;)。
而第一種 f1 實際上返回的是個匿名函數,這樣 n 作用域被另外個 f2 函數作用域所使用,因此它會保留。n 不會被重復聲明,且內容會被保存
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 | 檔案 | 引用
星光 說:
一文中的!!!!!!!!!!!!
學習了!!
2009年9月 3日 11:08 | 檔案 | 引用
zhaorui 說:
想知道思考題的答案,
我以為是:My Object
2009年9月15日 00:09 | 檔案 | 引用
steven 說:
頂樓主,我讀了一些文章。不是特明白。
有個問題。
記得有人說。外面的函數是closure,
好像樓主說里面的函數是closure.
不知道到底哪個是?謝謝。
2009年11月21日 14:51 | 檔案 | 引用
jkd___w 說:
樓主講講最后一個思考題,沒明白
2009年11月26日 09:21 | 檔案 | 引用
hou 說:
請版主講一講最后一個例子怎么回事,沒有看明白
2009年11月30日 16:00 | 檔案 | 引用
George Wing 說:
函數中的this一般是指向window中的變量。
2009年12月13日 09:55 | 檔案 | 引用
George Wing 說:
上面本人說得不太正確。
this的指向是由它所在函數調用的上下文決定的,而不是由它所在函數定義的上下文決定的。
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 說:
大道至簡,給予我這個初學者很大的幫助,謝謝!
2010年1月11日 09:30 | 檔案 | 引用
過客 說:
淺顯易懂,很好。
如下看法,認為有待商榷:
#1、有一個地方需要注意,函數內部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!
#2、這段代碼中另一個值得注意的地方,就是“nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個匿名函數本身也是一個閉包,所以nAdd相當于是一個setter,可以在函數外部對函數內部的局部變量進行操作。
function f1(){
test = 10;
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
//如果 #1 說法正確,下句會打印10,實際結果是test未定義。
//alert(test); // error test 未定義
//如果 #2 正確,語句 nAdd(); 位置在何處應該都能執行,測試結果在下面這個位置,也就是語句 var result=f1(); 前。是不能執行的。
//nAdd();
var result=f1();
result(); // 999
nAdd();
result(); // 1000
2010年1月28日 11:36 | 檔案 | 引用
ning 說:
To 過客:
函數內部定義的方法和變量,要等到函數執行過以后,才會真正定義
2010年3月20日 16:17 | 檔案 | 引用
Jason 說:
但是從過客說的里面可以引出另外的問題,當使用這樣的代碼時。
然后調用 則顯示為999。說明nAdd中的n確實是作為全局變量存在。于是問題就來了——有什么方法讓他可以是父函數中定義的n呢?function f1(){
test = 10;
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
如果在函數f1定義之前添加變量定義
?
2010年4月26日 15:28 | 檔案 | 引用
西子湖畔的樹人 說:
大道至簡,很不錯!~ 這篇文章我要轉了...
2010年4月28日 22:48 | 檔案 | 引用
iworm 說:
this關鍵字代表的實例會根據環境不同而變化的. 他總是指向owner 看看這篇你大概就動this這個關鍵字了
http://www.quirksmode.org/js/this.html
2010年4月29日 12:34 | 檔案 | 引用
tomwang 說:
最后一個題感覺和閉包沒什么關系啊,能詳細解釋一下嗎?因為當一個函數作為函數而不是方法來調用的時候,this指向的是全局對象,這在《Javascript權威指南》上說的很清楚,所以答案肯定是“The Window”,和閉包沒什么關系啊
2010年5月23日 18:24 | 檔案 | 引用
afity 說:
最后一題重點在this
2010年8月25日 23:26 | 檔案 | 引用
bao 說:
如果把f2申明成全局變量,道理一樣嗎?
2010年9月 6日 17:45 | 檔案 | 引用
小貓 說:
太經典了!
終于理解了,一箭雙雕啊!既理解了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() {//假設函數名為A
return function()/*假設函數名為B*/ { return this.name; };
}
在函數里面構建函數的時候,閉包產生。
在函數B內調用函數A的this.name,由于函數A沒有name屬性,所以就去找全局變量name,找到了,所以返回“The Window”,要是沒有找到,則返回“undefined”。
代碼段二可以嘗試將代碼更改為:
var _this = this;
return function() { return _this.name +"__"+ this.name; };
2010年12月13日 15:50 | 檔案 | 引用
LuckyGeb 說:
只有一點沒弄懂,如下代碼,nAdd在函數外為什么可以有意義?而test不行?想了好久,不知道那里有解答
function f1(){
test=10;
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
2011年1月17日 01:17 | 檔案 | 引用
qdsang 說:
通俗易懂,, 閱覽無數教程, 看了這篇, 終于明白了點.
2011年1月24日 13:21 | 檔案 | 引用
sf 說:
寫得太好了
2011年2月15日 16:47 | 檔案 | 引用
Luke 說:
這篇文章是阮兄一貫的風格,我喜歡,不過 "Javascript語言的特殊之處,就在于函數內部可以直接讀取全局變量。"這句有點奇怪,c不一樣可以在函數內部直接讀取全局變量么?難道不是么?
2011年3月 2日 22:02 | 檔案 | 引用
ignition 說:
阮大哥能不能具體講下最后的思考題啊? 感覺關鍵在this
2011年3月 8日 15:56 | 檔案 | 引用
三少爺 說:
變量的作用域無非就是兩種:全局變量和局部變量。
這句話值得商榷, 變量的作用域確實只有兩種, 不過他們是全局對象和函數.
你想說的或許是變量的類型有兩種?
2011年3月25日 20:15 | 檔案 | 引用
軒脈刃 說:
理解最后兩個例子:
1 函數中的this指的是調用這個函數的owner
2 object.getNameFunc()是返回一個函數,并沒有執行函數中的代碼
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(); //此時沒有執行this.name
var name = tmp();//這個時候才執行,這時候的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(); //這個時候執行了that = this,這里的this上下文是object,所以that指的是object
var name = Object.getNameFunc(); //這個時候執行了that.name
alert(name);
//alert(object.getNameFunc()());
2011年4月19日 15:28 | 檔案 | 引用
小秦 說:
你自己描述的是 f1()() 顯示的是999,說明 n 是使用的f1內部的變量n,而非是全局變量n,不知道你為什么會有
這種想法呢? 如果想在nAdd中使用全局變量n(即在函數外面定義的n)的話,使用window.n來訪問.?
2011年4月20日 14:56 | 檔案 | 引用
小秦 說:
樓主文章中的:
這一整大段中的
根據整篇文章所表達的內容,應該為:
因為如果沒有加var,則聲明的是全局變量,既然是全局變量,則在所有函數內部都是可見的,也就不會存在閉包這種說法.
?
請求樓主修正.
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 說:
起初以為函數內用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
看起來函數中的var和this并不是一個概念,函數內的局部變量與函數的屬性不是一回事,不過通過上面的情況能夠加深理解this和閉包
2011年4月22日 11:27 | 檔案 | 引用
Aizen 說:
很不錯的講解,樓主寫的通俗易懂,很棒的理解,很受用!我的qq:290913917 希望有機會成為共同研究javascript和html5的伙伴,謝謝!
2011年5月 1日 00:10 | 檔案 | 引用
lily 說:
我感覺第一個思考題是不是這樣理解:
首先this指向的是當前運行該函數的對象,
1、object.getNameFunc()得到了一個函數,函數為function(){return this.name}
2、object.getNameFunc()(),此時為window運行該函數,所以this指向的是window,所以this.name為The window
2011年5月 4日 11:29 | 檔案 | 引用
foxracle 說:
做習題之前有一點需要很清楚:
內部函數可以訪問定義它們的外部函數的參數和變量(除了this和arguments之外)
如果需要訪問對象的name屬性的話,就需要顯示的定義一個變量that來引用this,而這個變量此時就指向object對象了。
第一題改成下面這樣就很清楚了。getNameFunc的第一個()是屬于方法調用,所以this綁定到了object對象,自然this.name為"My Object",但是閉包函數無法訪問這個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 說:
閉包是運行時中的概念,不能講哪個函數是一個閉包!而是哪個函數在運行時存在一個閉包!有時候,好幾個函數都可以組成一個閉包呢:
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 說:
最后兩個例子中,第一個其實不是閉包,第二個是,但第二個例子其實不用那么復雜,直接把第一個例子中的this去掉就可以了。
2011年7月25日 14:02 | 檔案 | 引用
Revo 說:
為什么第一個運行以后結果是result?!既不是window也不是object....???
2011年8月12日 15:31 | 檔案 | 引用
?
?
?
轉自: http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html