对于希望在javascript技术中提高的人群来说,闭包肯定时常是一个令人感觉神秘的技术。早先有人说javaScript中的闭包可能会引发javaScript内存管理的复杂度,也许会出现内存泄露,所以不建议用闭包。不过jQuery很好的证明了闭包非常好用,C#的Linq也证明的闭包技术的重要性,所以花一点点时间来理解下闭包还是很值得的,再说了,以下的内容不过就是一杯茶的时间而已。
先给出一个闭包的定义:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
以上定义中非常重要的是
闭包关联到:函数和变量
闭包锁定了:函数和变量的环境关系
常有人在说闭包前会给出类似以下案例,说是javaScript的特殊性,函数中访问全局变量,这个我一直不太明白,一个函数访问它所在环境中的全局变量很特殊很奇怪吗?
var a = 10; function fun1() { alert(a); } fun1();
而且就算有以下代码我也丝毫不感觉有任何问题
var a = 10; function fun2() { b = 100;// 全局变量 } function fun1() { alert(a); alert(b); } fun2(); fun1();
真正值得你考量的是以下代码
function fun1(x) { var a = x; function fun2() { return a; } return fun2; } alert(fun1(100)()); //100 alert(fun1(9)()); //9
如果从结果来看,你也许同样不屑一顾,返回的值很明确的嘛。但是你仔细想想,问题就不简单了
函数fun1的结果是返回了一个fun2的函数。从代码来讲,fun1()是调用了fun1的执行,并且得到了fun2的函数,fun1()()就是说是执行fun2()了!如果你还是感觉正常的话,要么说明你已经理解闭包了,要么就是你忽略了一个重要的事实!
调用fun1()后,fun1所占用的内存应该已经释放了,fun1函数中的所有变量都将释放!!!对不?
function sum(x, y) { var a = x, b = y; return a + b; } alert(sum(9, 5));//14
上面的代码,当我完成sum(9,5)之后,sum函数肯定被释放了,a和b将不存在是不?如果你还是感觉不是很明晰的话,那么看下如下的分解动作吧。
function fun1(x) { var a = x; function fun2() { return a; } return fun2; } var fun3 = fun1(10); var fun4 = fun1(9); alert(fun4()); //9 alert(fun3()); //10
看看第一次的fun1将x的值赋值了10,第二次的fun1将x的值赋值为9,而且我们先执行了fun4,要求返还的值是9,再次执行fun3的得到的是10!!!说明什么呢?说明当fun2被创建的时候,它将它所需要用到的变量锁住 了,或者不这么夸张的说是记忆住了。
闭包就是函数在创建自己的时候,将需要用的变量锁住或说记忆住。
这里的函数创建不是指函数声明,而且指函数表达式被激活的时候,匿名函数表达式的激活有:call就是()调用,()分组,还有就是return的时候。
看看如下案例
var dofun = []; for (var i = 0; i < 10; i++) { dofun[i] = function() { return i; } } for (var j = 0; j < 10; j++) { alert(dofun[j]()); //全部返还10 }
很多人在网上用过这个案例来说明闭包的特性,我需要很严肃的声明两个问题
- 这个案例可以说明闭包
- 这个案例还有更神奇的特性说明
dofun[j]()
的时候才被激活,这个时候i的值是10,所以这个函数被返回了10,要解决这个问题,可以要求函数在i是各种值的时候被激活,怎么激活?return
var dofun = []; for (var i = 0; i < 10; i++) { dofun[i] = (function(k) { return function() { return k; } }(i)); } for (var j = 0; j < 10; j++) { alert(dofun[j]()); //0-9 }
这个处理中很有意思的是,我需要一个k来帮忙,为什么呢?
那就是我刚才说的第2点,先看下如下很令人无语的代码
var dofun = []; for (var i = 0; i < 10; i++) { dofun[i] = function() { return i; } } for (var i = 0; i < 10; i++) { alert(dofun[i]()); }
咋一看,你估计会暴跳起来,这不是耍人嘛,结果我们已经知道了,都是10!!!!错!!!!!!弹出的分别是0 1 2 3 4 5 6 7 8 9!!!
不相信你就测试一下看看。你仔细看看代码不同在哪里?下面的for用了变量是i,这就是不同所在,也是闭包的关键所在!
函数是不是值得来锁定一个变量,是看该变量在调用这个函数的时候,是不是能在上下文作用域中找到这个变量,如果无法在调用时找到这个变量,内部函数就会锁住它,否则就不会锁住,至少表面上是这样的。
现在我们再把目光移到最初的那个案例
function fun1(x) { a = x; function fun2() { return a; } return fun2; } var fun3 = fun1(10); var fun4 = fun1(9); alert(fun4()); //9 a = 999; alert(fun3()); //999
fun3()的结果是999的原因倒不是在乎现在a是全局变量,而是现在fun3在执行的时候能在上下文作用域中找到a了,全局变量不过是凑齐罢了。