zoukankan      html  css  js  c++  java
  • 解密JavaScript闭包

    译者按: 从最简单的计数器开始,按照需求对代码一步步优化,我们可以领会闭包的神奇之处。

    为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

    对于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);
    }
    

    多个计数器

    以上的代码非常简单。但是,当我们需要第二个计数器时,就会遇到问题了。当然,我们可以实现两个重复的计数器:

    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
    

    在代码中,我们创建了两个独立的计数器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

    Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了7亿+错误事件,得到了Google、360、金山软件、百姓网等众多知名用户的认可。欢迎免费试用!

    版权声明

    转载时请注明作者Fundebug以及本文地址:
    https://blog.fundebug.com/2017/07/31/javascript-closure/

  • 相关阅读:
    Best wishes for a wonderful new year.
    Using X++ code Reading to CSV file
    Types of delete action
    get focus from the FORM in dynamcis AX 2009
    Database Lock
    Using x++ code export to CSV file from dynamics AX 2009
    Using x++ code updated to system filed values
    Merry Christmas and Best Wishes for a Happy New Year
    the most reluctant to delete to New Year SMS
    《那些年啊,那些事——一个程序员的奋斗史》——53
  • 原文地址:https://www.cnblogs.com/fundebug/p/9915796.html
Copyright © 2011-2022 走看看