闭包是JavaScript特有的一种进阶代码技巧。
先看下面一个例子,注意对比两个方法的不同之处
function init() {
var name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init();
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
这两段代码执行后都会弹出出带有“Mozilla”字样的alert弹窗,让我们分析一下这两段代码。
init方法和makeFunc方法都有具有一个变量name,和一个内部方法displayName,所不同的是,init方法在内部执行了displayName方法,而makeFunc方法则是将displayName方法作为返回值返回。
我们知道,JS是有自己的垃圾回收机制的,JS方法中的局部变量只会在方法执行的周期中存在,当方法执行完成,局部变量应该被回收。但是,在上面的代码中,如果name在 var myFunc = makeFunc(); 执行之后就被回收,那么之后再执行myFunc方法,事实上就是执行displayName方法,此时name已被回收,结果将会是弹出undefined的alert弹窗。但是明显,执行结果告诉我们过程并非如此,在makeFunc这个方法里的name变量是没有被回收的,这是为什么呢?
事实上,是因为上面的makeFunc方法形成了一个闭包。
闭包实际是一个方法的内部方法,它能够访问“包裹”着它的方法的作用域链和执行环境。并且,当此内部方法的执行,需要外层作用域链的支持时,尽管外层方法已经执行结束,也不会被垃圾回收机制所回收。(个人理解)
让我们再看两个例子来加深对闭包定义的理解,以及了解闭包在实际使用中的价值。
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */
我们知道,JavaScript遵循的ECMA-262规范,并没有关于类的定义,也就没有私有方法这样的定义,而上面的代码,实际上利用了闭包的手段,模拟实现了私有方法。
在makeCounter方法外部,我们只能通过其返回的increment,decrement以及value方法获取其内部变量privateCounter的值,并进行规定的操作。这实际上就保护了privateCounter变量,同时没有直接将changeBy这个操作privateCounter变量的方法暴露出来,使之成为一个匿名方法,保护了代码。
同时,我们注意到,Counter1和Counter2是互相独立的。
另一个例子的情景是关于循环绑定事件的,这是很多初学者经常会遇到的问题。