计数器
首先,从一个计数器开始。
var counter = 0;
function increment() {
counter = counter + 1;
console.log("Number of events: " + counter);
}
increment(); // Number of events: 1
increment(); // Number of events: 2
increment(); // Number of events: 3
多个计数器
上面的代码简单粗暴,但是我们很快会遇到下一个问题:如果想要再创建一个计数器怎么办呢。当然我们可以创建两个变量,两个函数,但这样是不是太low了:
var counter1 = 0;
function incrementCounter1() {
counter1 = counter1 + 1;
console.log("Number of events: " + counter1);
}
var counter2 = 0;
function incrementCounter2() {
counter2 = counter2 + 1;
console.log("Number of events: " + counter2);
}
incrementCounter1(); // Number of events: 1
incrementCounter2(); // Number of events: 1
incrementCounter1(); // Number of events: 2
而且当需要更多的计数器时,就更不可能用这种方法了。
引入闭包
上面的那段代码,我们更想做的是封装成一个函数,去除冗余代码。下面我们就初步尝试一下闭包:
function createCounter() {
var counter = 0;
function increment() {
counter = counter + 1;
console.log("Number of events: " + counter);
}
return increment;
}
现在来看一下发生了什么。我们将会创建两个计数器,并且使用它们来跟踪两个独立的事件:
var counter1 = createCounter();
var counter2 = createCounter();
counter1(); // Number of events: 1
counter1(); // Number of events: 2
counter2(); // Number of events: 1
counter1(); // Number of events: 3
额,看起来有点复杂。。但实际上很简单。我们只需要分解一下实现的逻辑。
首先,创建了一个局部变量counter
然后,创建了一个局部函数increment
,可以增加counter
的值。
其实这个createCounter()
的实现几乎跟最开始那个计数器一样。唯一不同的就是它被函数包裹起来了。于是,这种结构就被称作闭包。
然后就到了最重要的地方:
createCounter()
里的最后一步 返回了 increment
局部函数 ,注意,这里返回的不是函数的调用结果,而是函数本身。
这也就是说,当我们使用下面的语句创建计数器,我们实际上是生成了一个新的函数。
// fancyNewCounter is a function in this scope
var fancyNewCounter = createCounter();
这就是闭包强大的地方。每个通过createCounter()
生成的函数都保持追踪它们自己产生的counter
的值。也就是说,这个返回的函数会记住他被创建时的环境。
可以看到的重要的一点就是内部的counter变量都是互相独立的。创建了两个计数器,它们会各自在闭包中分配一个新的counter变量。我们可以看到:
每个计数器都从1开始计数:
var counter1 = createCounter();
counter1(); // Number of events: 1
counter1(); // Number of events: 2
var counter2 = createCounter();
counter2(); // Number of events: 1
第二个计数器没有收到第一个计数器的值的影响。
counter1(); // Number of events: 3
命名我们的计数器
“Number of events: x”这种消息是可以的,但是如果信息可以描述我们正在计数的事件的类型,会不会更好。例如,如果我们可以在我们的计数器里面加一个名字:
var catCounter = createCounter("cats");
var dogCounter = createCounter("dogs");
catCounter(); // Number of cats: 1
catCounter(); // Number of cats: 2
dogCounter(); // Number of dogs: 1
我们可以往闭包里传一个参数
function createCounter(counterName) {
var counter = 0;
function increment() {
counter = counter + 1;
console.log("Number of " + counterName + ": " + counter);
}
return increment;
}
可以看出来,createCounter在实现过程中不仅能记住局部变量counter,也记住了传进来的变量。
优化公共接口
上面的写法有个问题就是,当我们在执行计数器的时候,很难直观的看出来这个计数器是个将要计算增量的函数,下面的写法会更简洁:
var dogCounter = createCounter("dogs");
dogCounter.increment(); // Number of dogs: 1
function createCounter(counterName) {
var counter = 0;
function increment() {
counter = counter + 1;
console.log("Number of " + counterName + ": " + counter);
};
return { increment : increment };
}
在上面的代码中,我们返回了一个包含闭包中所有函数的对象。从某种意义上来说,我们正在定义我们的闭包可以响应的一系列消息
添加递减
现在我们可以非常简单的将递减的函数加入到计数器中。
function createCounter(counterName) {
var counter = 0;
function increment() {
counter = counter + 1;
console.log("Number of " + counterName + ": " + counter);
};
function decrement() {
counter = counter - 1;
console.log("Number of " + counterName + ": " + counter);
};
return {
increment : increment,
decrement : decrement
};
}
var dogsCounter = createCounter("dogs");
dogsCounter.increment(); // Number of dogs: 1
dogsCounter.increment(); // Number of dogs: 2
dogsCounter.decrement(); // Number of dogs: 1
隐藏计数动作
上面的代码的console.log重复了两次。应该明确的创建一个函数来显示计数器的值。
function createCounter(counterName) {
var counter = 0;
function display() {
console.log("Number of " + counterName + ": " + counter);
}
function increment() {
counter = counter + 1;
display();
};
function decrement() {
counter = counter - 1;
display();
};
return {
increment : increment,
decrement : decrement
};
}
var dogsCounter = createCounter("dogs");
dogsCounter.increment(); // Number of dogs: 1
dogsCounter.increment(); // Number of dogs: 2
dogsCounter.decrement(); // Number of dogs: 1
display
函数看起来和increment()
和decrement()
很像的样子,但其实是非常不同的。我们没有在对象的结果中返回这个函数,也就是说下面的语句会报错:
var dogsCounter = createCounter("dogs");
dogsCounter.display(); // ERROR !!!
我们让display()
函数从外面的世界隐藏了,它只供createCounter()
内部访问
抽象数据类型
让我们使用闭包来实现栈操作
function createStack() {
var elements = [];
return {
push: function(el) { elements.unshift(el); },
pop: function() { return elements.shift(); }
};
}
var stack = createStack();
stack.push(3);
stack.push(4);
stack.pop(); // 4
注意:在Javascript中,闭包可能并不是实现栈数据类型的最佳实现方式,Prototypes可能会更好一些
闭包和OOP
如果你做过面向对象编程,你可能会注意到,上面的结构特别像类啊对象啊实例变量和公有/私有方法。
按:好久没翻译了,这篇文章翻译的有些智障,但是不耽误看代码,看完还是很有收获的!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。