1. 完成计数器闭包
JS的闭包是一个强大的功能,最基础的利用闭包的例子是完成一个封装的计数器。
我们可以先来看一下如何用朴素的思想完成计数器。
var counterValue = 0;
var counter = function() {
return (++counterValue);
};
counter(); // 1
counter(); // 2
这样,我们每一次调用counter()
,都将counterValue
计数加一。
这样做有一个弊端,那就是在函数外部我们可以干扰计数的进行,比如
counterValue = 5;
counter(); // 6
很自然的我们想到要把这个计数变量封装到某个对象的内部并且使得在其他地方(除了counter()
)都无法访问到这个变量也无法修改这个变量。如何做呢?
var counter = function() {
var counterValue = 0;
return function() {return (++counterValue);};
};
值得注意的是,如果我们使用counter()
,那么我们将得到一个函数,如果想要得到计数值,我们还要调用这个函数,即counter()()
。
counter()(); // 1
counter()(); // 1
和预想中效果的不太一样,不过倒也不出意料之中。我们分析一下这个语句。
在每次执行counter()()
时,都要先执行counter()
得到一个函数,在执行中,var counterValue = 0
,然后在运行{return (++counterValue);}
,得到1。
看来不能每次重置counterValue = 0
,我们要让他跳过函数的第一步,那么我们免除每次用counter()
得到这个函数的过程。
var anotherCounter = counter();
anotherCounter(); // 1
anotherCounter(); // 2
Perfect! 我们完成了一个函数闭包,counter的计数变量被封装在了一个看不见的地方,只有它自己能找到。每次运行anotherCounter();
,计数都会增加。
看起来很棒了,不过我们还能稍稍的改进一下,那就是JS的IIFE(Immediately Invoked Function Expression)——立即执行函数。
var counter = function() {
var counterValue = 0;
return function() {return (++counterValue);};
}();
在函数后边加一个圆括号,那么这个函数会被立即调用一次,这样counter
变量就得到了一个与上一段代码中的anotherCounter
一样的函数,我们也就不需要再去多写那一行代码和多命名一个变量了。准确的说这种方式和上一种方式并没有太大的优缺点,不过在应用环境不同的时候,两种方式有着不同的优势。比如第一种方法,我们可以定义多个互不干扰的计数器同时运行。第二种方法则减少了干扰。
2. 分析
看起来很神奇,JS是怎么记住这个计数变量的值的呢?我们可以简单的分析一下。
我们还是要用anotherCounter()
的例子来讲。
首先
var anotherCounter = counter();
我们运行了counter()
,在其中我们定义了counterValue
,在counter()
返回的函数中,有着对于这个值的引用,所以在anotherCounter
这个函数或者称为对象中,也包含着对于counterValue
的引用。所以,counterValue
这个看似是counter
内的局部变量的东西在函数结束后并没有被销毁,而是依然保留了下来,正是因为现存的对象中,anotherCounter
依然有对他的引用。这样,我们在下一次运行anotherCounter
时,它依然在改变它内部关联到的counterValue
,这样就实现了计数功能。
如果我们再给新的一个counter()
呢?比如
var anotherCounter1 = counter();
anotherCounter(); // 1
anotherCounter(); // 2
anotherCounter1(); // 1
anotherCounter1(); // 2
可以看到两个计数器独立工作,因为他们内部的counterValue
是不同的counterValue
,每次运行时,两个引用并不一样。