译者按: 从最简单的计数器开始,按照需求对代码一步步优化,咱们能够领会闭包的神奇之处。javascript
原文: Closures are not magicjava
译者: Fundebug编程
为了保证可读性,本文采用意译而非直译。另外,本文版权归原做者全部,翻译仅用于学习。闭包
对于JavaScript新手来讲,闭包(Closures)是一个很神奇的东西。这篇博客将经过一个很是浅显的代码示例来解释闭包。函数
咱们的目标是实现一个计数器,它的效果以下:学习
increment(); // Number of events: 1 increment(); // Number of events: 2 increment(); // Number of events: 3
可知,每次执行increment()都会输出"Number of events: N",且N每次都会加1。优化
这个计数器最直观的实现方式以下:翻译
var counter = 0; function increment() { counter = counter + 1; console.log("Number of events: " + counter); }
以上的代码很是简单。可是,当咱们须要第二个计数器时,就会遇到问题了。固然,咱们能够实现两个重复的计数器:debug
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
显然,以上的代码很是冗余,有待优化。当咱们须要更多计数器时,使用这种方法将不太现实。这时,就须要神奇的闭包了。code
须要多个计数器,同时但愿去除冗余代码的话,就可使用闭包了:
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
在代码中,咱们建立了两个独立的计数器counter1与counter2,分别进行计数,互不干挠。代码看着有点奇怪,咱们不妨拆分起来分析。
首先,咱们来看看createCounter:
看起来,**createCounter()**函数与咱们最初定义的计数器很是类似。惟一的不一样点在于:createCounter()将计数器封装在一个函数内,因而咱们将它称做闭包。
难以理解的一点在于,当咱们使用**createCounter()**函数建立计数器时,实际上建立了一个新的函数:
// fancyNewCounter是一个新建立的函数 var fancyNewCounter = createCounter();
闭包的神奇之处在于。每次使用createCounter()函数建立计数器increment时,都会建立一个对应的counter变量。而且,返回的increment函数会始终记住counter变量。
更重要的是,这个counter变量是相互独立的。好比,当咱们建立2个计数器时,每一个计数器都会建立一个新的counter变量:
// 每一个计数器都会从1开始计数 var counter1 = createCounter(); counter1(); // Number of events: 1 counter1(); // Number of events: 2 // 第1个计数器不会影响第2个计数器 var counter2 = createCounter(); counter2(); // Number of events: 1 // 第2个计数器不会影响第1个计数器 counter1(); // Number of events: 3
多个计数器的输出信息都是**“Number of events: N”**,这样容易混淆。若是能够为每一个计数器命名,则更加方便:
var catCounter = createCounter("cats"); var dogCounter = createCounter("dogs"); catCounter(); // Number of cats: 1 catCounter(); // Number of cats: 2 dogCounter(); // Number of dogs: 1
经过给createCounter传递一个新的counterName参数,能够很容易地作到这一点:
function createCounter(counterName) { var counter = 0; function increment() { counter = counter + 1; console.log("Number of " + counterName + ": " + counter); } return increment; }
这样,createCounter()函数返回的计数器将同时记住两个局部变量:counterName与counter。
按照以前的实现方式,咱们经过调用createCounter()函数能够返回一个计数器,直接调用返回的计数器就能够加1,这样作并不直观。若是能够以下调用将更好:
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 }; }
可知,以上的代码返回了一个对象,这个对象包含了一个increment方法。
如今,咱们能够给计数器添加一个**decrement()**方法
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语句。所以,咱们能够建立一个display()方法用于打印counter的值:
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()函数差很少,可是其实它们很不同。咱们并无将display()**函数添加到返回的对象中,这就意味着如下代码会出错:
var dogsCounter = createCounter("dogs"); dogsCounter.display(); // ERROR !!!
这时,**display()至关于一个私有方法,咱们只能在createCounter()**函数内使用它。
若是你接触过**面向对象编程(OOP),则应该不难发现本文中所涉及的内容与OOP中的类**、对象、对象属性、共有方法与私有方法等概念很是类似。
闭包,与OOP类似,就是把数据和操做数据的方法绑定起来。所以,在须要OOP的时候,就可使用闭包来实现。
**闭包(Closure)**是JavaScript一个很是棒的特性。掌握它,咱们能够从容应对一些常见的编程需求。
版权声明:
转载时请注明做者Fundebug以及本文地址: