JavaScript闭包
写一下闭包到底是个什么东西,看了无数的博客,无数的例子,基本上还没有看到用简单的方式来说明闭包,我试着写写,没有新的东西,基本都是前辈资料的总结。抛个砖先。热烈欢迎大神们斧正。
按照惯例,先看下官方是如何定义的
Closures are functions that refer to independent (free) variables.
In other words, the function defined in the closure 'remembers' the environment in which it was created in.
- 第一句:闭包是函数,什么样的函数?涉及到独立变量的函数。什么叫“独立变量”?我也不明白,一会边写边说。
- 第二句:换言之,这个函数是在闭包的‘记忆’环境定义。“记忆环境”是什么东东?还是不明白。
好吧,以我苦逼的英语水平,就只能这样理解了。但这说了什么?我不明白,一点都不明白。
链接在这里,英语好的少年可以去看看。
再看另一个定义
A "closure" is an expression (typically a function) that can have free variables together with an environment
that binds those variables (that "closes" the expression)
http://jibbering.com/faq/notes/closures/
闭包是一个表达式(通常是一个函数),什么样的表达式?拥用自由变量和一个绑定这些变量的环境的表达式。
这个定义相对要明白一些,但还是不太具体。
再看看wiki
In programming languages, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.A closure—unlike a plain function pointer-allows a function to access those non-local variables even when invoked outside its immediate lexical scope.
http://en.wikipedia.org/wiki/Closure_(computer_programming)
好吧,又是乱七八糟的一大堆,但是最后一句很关键:闭包不像一般函数,它允许一个函数访问这些变量,即使这些引用超出了变量的作用哉。
诸位不要觉得看定义是很烦人,是没有必要的。在我看来这是非常非常重要的东西。因为大多知识点,在定义上面都写得很清楚。不过closures算一个例外吧,因为它本来就很难说清楚......
既然国外文献定义得不太明白,就看看国内的先辈们有没有好的定义,摘录如下:
- 闭包就是能够读取其他函数内部变量的函数。
- 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。
- 闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。
- 闭包是定义在另一个函数中的匿名函数。(javascript详解第二版 ——Ellie Quigley Page 124)
对于第3条,是来自百度百科,看了之后我只能说:壮哉我大百度!还有比这更详尽的定义么?有发现的请发给我!这里对自由变量有个解释:未绑定到特定对象的变量。
大部份程序员由于逼格原因,是鄙视百度的,动辙google,跳辙wiki。殊不知知识莫问出处,好用就行。
这里多说两句,除了一些新的知识,国内论坛基本都有非常详尽的资料,如果你没有达到国内的资料已经不有满足你的程度,就不要到处找英文资料,因为母语资料理解起来,肯定比英文快得多。
嗯,直白点说就是:在程序这一块,英语是用来用的,而不是用来show的。
所以,我们总结一下什么是闭包:
-
什么时候需要用到闭包:
- 需要在一个函数外部,访问函数内部的变量的时候(也就是说在函数运行完之后,你想要把变量保存下来,待需要的时候调用。而不是通过垃圾回收机制消除(garbage collection))。
- 保护变量安全。一个函数的内部变量,只能内部函数引用。
-
如何定义闭包:
- 在一个函数内部,定义一个函数,并返回一个函数的引用。
如何在外部引用一个函数内部的变量?
function a(){
var i = 1;
}
alert(i); //undifined
// 为什么这样?因为一个变量的作用域只在一个函数本身,这是javascript最基本的入门的概念,想来不用多说。
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c = a();
c(); //1;
这个例子来自于百度百科,有三个关键点
- b是定义在a的内部
- a的运行结果是 return b。此时b就一直保存在内存当中,直到你手动删除为止。
- b引用了a的变量(这一点极其重要)。
所以,c = a();其实调用的就是b,由于b是在a的内部,所以就可以访问i(这是javascript的链接作用域(chain scope),内部函数可以访问外部函数的变量)。
这样,就达到了在a的外部,调用a中的变量。再有,a中定义的函数是b,而不是一个匿名函数,但是这样是可行的。所以,上面第4条的定义就是错的,闭包并不一定是一个匿名函数。
这一个例子,基本就已经说完了闭包了。但是看的人可能还是没有一个明确的概念,那么就继续看几个例子。
function a(){
var i = 1;
function b(){
alert(i);
}
i++;
return b;
}
var f = a();
f(); // 2
为什么呢是2呢?
return b是在a的最后执行,所以,在ruturn b的时候,已经将a中代码全部执行了,所以 i=2;若在i++前返回b。
f()的结果就是1。这就可以推出:也可以把 var i = 1;写在function b的后面。只要写在return之前即可。
function a(){
var i = 1;
b = function(){alert(i)}
c = function(){i++}
d = function(j){i = j}
}
a(); //运行一次a,为b\c\d赋值。
b(); // 1
c(); // i++
b(); // 2
d(3); // i = 3
b(); //3
- 此处应有三点需要说明:
- 在函数内部定义变量一定要用var,否则定义的就是一个全局变量。在这里b c d皆没有用var,所以就可以在外部直接调用这三个全局变量
- a需要运行一次,才会给bcd赋值。否则会报错 b c d未定义
- b c d 都在a内,所以都可以调用变量i.
循环中的闭包
function closureInLoop(Ar){
var result = [];
for(var i=0;i<Ar.length;i++){
var item = 'item' + Ar[i];
result.push(function(){alert(item + ' ' + Ar[i])});
}
return result;
}
//
function test(){
var fnList = closureInLoop([1,2,3]);
alert(fnList);
for(var j=0;j<fnList.length;j++){
fnList[j]();
}
}
test(); // item3 undifined(3次);
我们原以为,会依然出现item 1,item 2,item 3。结果却不同,这是什么原因?
先把fnList输出来看看
[fnction(){alert(item + ' ' + Ar[i])},function(){alert(item + ' ' + Ar[i])},function(){alert(item + ' ' + Ar[i])},]
三个一模一样的function,都是调用的Ar[i]。此时i是多少呢?看下前面的closureInLoop的for循环。
传入的参数是[1,2,3],所以closureInLoop中的Ar.length就是3。所以for执行完的时候:
i=3;
item = 'item' + Ar[2];也就是item3;由于return result了。所以此时resulte里面的function就形成了闭包。
三个function都引用了i和item。此时i=3,item = item3;
所以都是function(){item + ' ' + Ar[3]}
但是Ar传入的是[1,2,3],Ar.length = 3 没错。
但是Ar[0] = 1;Ar[1] = 2; Ar[2] = 3....都没错。但是骚年们,Ar[3] = undifined;
alert(item + ' ' + Ar[3]) = item3 undifined。Over,这个太麻烦,我想也没几个人这样写吧。不过这个例子非常经典,可以仔细看看。
例外一种循环里的闭包
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i);
},1000);
}
// 这样并不能依次输出i,而是输出10次10。
每次都创建一个新的闭包
function createClosure(number,reference){
var num = number,
ref = reference,
anArray = [1,2,3];
return function(x){
num += x;
anArray.push(num);
alert(num + ' ' + anArray.toString() + ' ' + ref);
}
}
closure_1 = createClosure(20,'ref_1');
closure_1(10); //30 [1,2,3,30] ref_1
//
closure_2 = createClosure(100,'ref_2');
closure_2(-10); //90 [1,2,3,90] ref_2
说好的闭包呢,为什么没有继续用closure_1的nunber和reference?
因为我们每建立一个新的闭包,都重新将number和reference通过参数改变。
参考资料
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
http://jibbering.com/faq/notes/closures/
http://en.wikipedia.org/wiki/Closure_(computer_programming)
http://baike.baidu.com/view/648413.htm
http://coolshell.cn/articles/6731.html
http://bonsaiden.github.io/JavaScript-Garden/zh/