资讯专栏INFORMATION COLUMN

【译】闭包并不神秘

sevi_stuo / 1630人阅读

摘要:下面我们就初步尝试一下闭包现在来看一下发生了什么。于是,这种结构就被称作闭包。这就是闭包强大的地方。例如,如果我们可以在我们的计数器里面加一个名字我们可以往闭包里传一个参数可以看出来,在实现过程中不仅能记住局部变量,也记住了传进来的变量。

计数器

首先,从一个计数器开始。

var counter = 0;
function increment() {
    counter = counter + 1;
    console.log("Number of events: " + counter);
}
increment();  // Number of events: 1
increment();  // Number of events: 2
increment();  // Number of events: 3
多个计数器

上面的代码简单粗暴,但是我们很快会遇到下一个问题:如果想要再创建一个计数器怎么办呢。当然我们可以创建两个变量,两个函数,但这样是不是太low了:

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

额,看起来有点复杂。。但实际上很简单。我们只需要分解一下实现的逻辑。

首先,创建了一个局部变量counter
然后,创建了一个局部函数increment,可以增加counter的值。

其实这个createCounter()的实现几乎跟最开始那个计数器一样。唯一不同的就是它被函数包裹起来了。于是,这种结构就被称作闭包。

然后就到了最重要的地方:

createCounter()里的最后一步 返回了 increment局部函数 ,注意,这里返回的不是函数的调用结果,而是函数本身。

这也就是说,当我们使用下面的语句创建计数器,我们实际上是生成了一个新的函数。

// fancyNewCounter is a function in this scope

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

第二个计数器没有收到第一个计数器的值的影响。

counter1(); // Number of events: 3
命名我们的计数器

“Number of events: x”这种消息是可以的,但是如果信息可以描述我们正在计数的事件的类型,会不会更好。例如,如果我们可以在我们的计数器里面加一个名字:

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 = counter + 1;

    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 = counter + 1;

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


  return { increment : increment };
}

在上面的代码中,我们返回了一个包含闭包中所有函数的对象。从某种意义上来说,我们正在定义我们的闭包可以响应的一系列消息

添加递减

现在我们可以非常简单的将递减的函数加入到计数器中。

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重复了两次。应该明确的创建一个函数来显示计数器的值。

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()很像的样子,但其实是非常不同的。我们没有在对象的结果中返回这个函数,也就是说下面的语句会报错:

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中,闭包可能并不是实现栈数据类型的最佳实现方式,Prototypes可能会更好一些

闭包和OOP

如果你做过面向对象编程,你可能会注意到,上面的结构特别像类啊对象啊实例变量和公有/私有方法。

按:好久没翻译了,这篇文章翻译的有些智障,但是不耽误看代码,看完还是很有收获的!

英文原文:http://renderedtext.com/blog/...

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

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

相关文章

  • 2017-08-03 前端日报

    摘要:前端日报精选专题之通用遍历方法的实现深入了解的子组件上最流行的项目再聊移动端页面的适配译盒子模型实践教程中文全栈第天数据驱动龙云全栈译年开发趋势疯狂的技术宅在翻译译闭包并不神秘前端心得拼多多前端笔试个人文章容器技术方 2017-08-03 前端日报 精选 JavaScript专题之jQuery通用遍历方法each的实现深入了解React的子组件GitHub上最流行的Top 10 Jav...

    gecko23 评论0 收藏0
  • 】深入研究Laravel的依赖注入容器

    摘要:原文地址下面是中文翻译拥有强大的控制反转依赖注入容器。单例在使用自动绑定和时,每次需要时都会创建一个新的实例或者调用闭包。 原文地址 Laravels Dependency Injection Container in Depth 下面是中文翻译 Laravel拥有强大的控制反转(IoC)/依赖注入(DI) 容器。不幸的是官方文档并没有涵盖所有可用的功能,因此,我决定尝试写文档为自...

    chavesgu 评论0 收藏0
  • 再议javascript闭包

    摘要:在中闭包已经成了一个很神秘,让人高山仰止的存在。今天又对闭包进行了一番搜索,有了一种明悟闭包就相当于黑盒的钥匙。当你手握闭包,黑盒就能为你所用。通过访问外部变量,一个闭包可以维持这些变量。闭包经常用于创建含有隐藏数据的函数但并不总是这样。 在javascript中闭包已经成了一个很神秘,让人高山仰止的存在。 今天又对闭包进行了一番搜索,有了一种明悟:闭包就相当于黑盒的钥匙。当你手握闭包...

    abson 评论0 收藏0
  • 】JavaScript数据结构(1):什么是数据结构

    摘要:它到底是什么,是作用于数据的结构吗这同样是一个模棱两可的术语。最后我还是搞清楚了数据结构的概念,那就简单的把术语数据结构称为数据的结构。目标数据结构系列技术文章,会告诉你数据结构并不是晦涩难懂的,更不是神秘的。 翻译:疯狂的技术宅英文:https://code.tutsplus.com/tut...说明:本文翻译自系列文章《Data Structures With JavaScrip...

    K_B_Z 评论0 收藏0
  • 】看权威的wikipedia如何解释闭包

    摘要:准确的说,是形成了的闭包。因此对象函数也是对象和控制流能通过闭包实现。这种方式的闭包不再具有引用透明性,即他不再是一个纯函数。类闭包结构一些语言的特性能够模拟出闭包的效果。 写在开头  本来是很讨厌谈论闭包这个话题的,因为在这一方面我比较倾向于玉伯还有一些朋友的观点,搞懂作用域才是最重要的,单独谈论闭包,真的意义不大。  今天刚好在wiki上查其他东西的时候看到了,想了想以前也没从比较...

    Alliot 评论0 收藏0

发表评论

0条评论

sevi_stuo

|高级讲师

TA的文章

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