闭包(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()());
(完)
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