解密JavaScript闭包

译者按: 从最简单的计数器开始,按照需求对代码一步步优化,咱们能够领会闭包的神奇之处。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

在代码中,咱们建立了两个独立的计数器counter1counter2,分别进行计数,互不干挠。代码看着有点奇怪,咱们不妨拆分起来分析。

首先,咱们来看看createCounter

  • 建立了一个局部变量counter
  • 建立了一个局部函数increment(),它能够对counter变量进行加1操做。
  • 将局部函数**increment()**返回。注意,返回的是函数自己,而不是函数调用的结果。

看起来,**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()函数返回的计数器将同时记住两个局部变量:counterNamecounter

优化计数器调用方式

按照以前的实现方式,咱们经过调用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方法

如今,咱们能够给计数器添加一个**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以及本文地址:

https://blog.fundebug.com/2017/07/31/javascript-closure/

相关文章
相关标签/搜索