资讯专栏INFORMATION COLUMN

解密JavaScript闭包

Maxiye / 1764人阅读

摘要:这时,就需要神奇的闭包了。将局部函数返回。唯一的不同点在于将计数器封装在一个函数内,于是我们将它称作闭包。闭包,与相似,就是把数据和操作数据的方法绑定起来。总结闭包是一个非常棒的特性。

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

原文: Closures are not magic

译者: 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);
}
多个计数器

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

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以及本文地址:
https://blog.fundebug.com/201...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84716.html

相关文章

  • 解密 JavaScript 执行上下文

    摘要:闭包就好像从中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人才能到达那里。兴奋地赶紧自测咔咔咔连点三下。结果当时内心表情大概就像上面这个哥们。但还是在工位上故作镇定地赶紧百度了下。   闭包就好像从JavaScript中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人才能到达那里。——《你不知道的JavaScript 上卷》 1、起源 js闭包很长一...

    khlbat 评论0 收藏0
  • JavasScript重难点知识

    摘要:忍者级别的函数操作对于什么是匿名函数,这里就不做过多介绍了。我们需要知道的是,对于而言,匿名函数是一个很重要且具有逻辑性的特性。通常,匿名函数的使用情况是创建一个供以后使用的函数。 JS 中的递归 递归, 递归基础, 斐波那契数列, 使用递归方式深拷贝, 自定义事件添加 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果...

    forsigner 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0
  • PHPer面试指南-Web 篇

    摘要:扩展阅读收集的前端面试题和答案前端开发面试题史上最全的前端面试题汇总及答案前端工程师手册协议工作原理协议运行机制的概述 本书的 GitHub 地址:https://github.com/todayqq/PH... 对于大公司,很少会有全栈工程师这个岗位,全栈是个花哨的词,对于现在比较热门的技术,不论是 Vue 还是 Laravel,只要智商不差,看着文档,都能写出一个 CURD 来,...

    cnio 评论0 收藏0
  • 2017-08-08 前端日报

    摘要:前端日报精选一行代码的逆向工程译只需四个步骤使用实现页面过渡动画如何实现一个基于的模板引擎解剖组件的多种写法与演进深入理解笔记扩展对象的功能性中文基础系列一之实现抽奖刮刮卡橡皮擦掘金小游戏个人文章和最常用的特征众成翻译常用语法总 2017-08-08 前端日报 精选 一行 JavaScript 代码的逆向工程【译】只需四个步骤:使用 React 实现页面过渡动画如何实现一个基于 DOM...

    alin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<