码迷,mamicode.com
首页 > 其他好文 > 详细

其实闭包并不高深莫测

时间:2015-12-20 13:06:13      阅读:159      评论:0      收藏:0      [点我收藏+]

标签:

原文来自微信公众号"前端大全"-伯乐在线/刘健超

计数事件--我们将从一个简单的问题开始。如果将闭包引入到该程序中,将能轻易解决这个问题。

我们为计数事件创建一个机制,该机制将有助于我们跟踪代码的执行,甚至去调试一些问题。

例如:

  increment();//Number of events:1

  increment();//Number of events:2

  increment();//Number of events:3

正如你所看到的上述案例,我们希望代码会在我们每次执行increment()函数时,会显示一条信息"Number of events:n"。

下面以简单的方式实现该函数:

var counter=0;

function increment(){

  counter+=1;

  console.log("Number of events:"+counter);

}

多个计数器--上述代码非常简单明确。然而,当我们引入第二个计数器时,很快就会遇到问题。当然,我们能实现两个单独的计数器机制。

如下代码:

var counter1=0;

function incrementCounter1(){

  counter1+=1;

  console.log("Number of events :" +counter1);

}

var counter2=0;

function incrementCounter2(){

  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+=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

 //fancyNewCounter 是当前作用域的一个函数

 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

 第二个计数器并不会干扰第一个计数器的值:

 counter();//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

我们仅需要通过为闭包传递参数就能达到这种目的。

  function createCounter(counterName){

    var counter=0;

    function increment(){

      counter++;

      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++;

      console.log("Number of"+counterName+":"+counter);

    }

   return{increment:increment};

  }

在上述代码段,我们简单的返回了一个对象,该对象包含了该笔报的所有功能。在某种意义下,我们能定义闭包能返回的一系列信息。

增加一个减量

 现在,我们能非常简单的为我们的计数器引入减量(decrement)。

  function createCounter(counterName){

    var counter=0;

    function increment(){

      counter++;

       console.log("Number of" +counterName+":"+counter);

    }

    function decrement(){

      counter--;

      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

隐藏计数器行为

  创建一个专门用于显示计数器值得函数将会更好,让我们调用display函数。

  function createCounter(counterName){

   var counter=0;

   function display(){

    console.log("Number of"+counterName+":"+counter);

   } 

    function increment(){

      counter++;

      display();

    }

    function decrement(){

      counter--;

      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

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中,闭包并不是堆栈数据类型最佳实现方式。用Prototype实现会对内存更友好(在当前对象实例找不回相应属性或方法时,会到相应实例共同引用的Prototype属性寻找相应属性或方法(如果在当前Prototype属性找不到时,会沿着当前原型链向上查找),而Prototype上的属性或方法时公用的,而不像实例的属性或方法那样,个子单独创建属性或方法,从而节省更多的内存)。

  闭包与面向对象编程

 如果你具有面向对象编程的经历,那么你应该会注意到上诉构造器看来非常像类、对象、实例值和私有/公有方法。

 闭包与类相似,都会有一些能操作内部数据的函数联系在一起。因此,你能在任何地方像使用对象一样使用闭包。

  结语

 闭包是编程语言一个很棒的属性。当我们想在JavaScript创建"真正的"隐藏域,或者需要创建简单的构造器时,闭包这个属性是非常好用的。不过对于一般的类来说,闭包可能还是有点太重了。

其实闭包并不高深莫测

标签:

原文地址:http://www.cnblogs.com/webljx/p/5060633.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!