没学过JavaScript也能看懂的闭包解释

栏目: JavaScript · 发布时间: 5年前

内容简介:有一句甚广的话,相信很多人都听过:假如有一台老式的有线的拨号座机,然后电话铃响起了,我们拿起电话听筒接电话。这个过程大约是这个样子:我们再具体分析一下这个场景。使用座机接电话时,我们都是拿起听筒来听电话,所以和我们的嘴巴与耳朵打交道的部分其实就是听筒。我们不需要关心电话主机的工作原理,更不用关心主机是如何接收到我们的声音信号,再把声音信号转换成电信号传输出去。我们又不是发明电话的贝尔,我们关心这些干什么?只要拿起听筒说话就好了。

有一句甚广的话,相信很多人都听过: 艺术源于生活又高于生活 ,意思是说艺术并不是凭空产生的,而是广大人民群众从日常生活中提炼出来的抽象表现。比如舞蹈,就是人们从具体劳动、生活、战争中抽象出来的肢体表达形式。而技术呢,也不是凭空产生的,也是源于生活,但是确是生活的低层次的抽象(或者叫模仿更加贴切)。所以说, 技术源于生活但低于生活 。典型的案例,就是面向对象思想,它就是对现实的世界对象的低层次抽象。为什么是低层次的?因为我们无法用代码表达出事物的每一个细节啊。

一个源于生活的场景

假如有一台老式的有线的拨号座机,然后电话铃响起了,我们拿起电话听筒接电话。这个过程大约是这个样子:

没学过JavaScript也能看懂的闭包解释

我们再具体分析一下这个场景。使用座机接电话时,我们都是拿起听筒来听电话,所以和我们的嘴巴与耳朵打交道的部分其实就是听筒。我们不需要关心电话主机的工作原理,更不用关心主机是如何接收到我们的声音信号,再把声音信号转换成电信号传输出去。我们又不是发明电话的贝尔,我们关心这些干什么?只要拿起听筒说话就好了。

但是,我们只关心听筒,并不意味着光有听筒就能完成打电话这件事情。我把听筒和主机之间的连线剪短了,我们光拿一个听筒说话,能完成接打电话这件事情么?显然不能啊。所以,我们关心什么东西是一回事,能不能完成这件事情是另一回事。听着耳熟对不对,我们前端向来不就是如此么?我们只关心有没有数据,有数据了,我就给你把内容显示在浏览器里,至于这个数据从哪里来的,我根本不关心。但是,如果这个数据不是真实的从服务端传递过来的,而是前端自己硬编码写的假数据,那么这个产品也就根本无法上线。所以说,有些事情,我们不关心它,但是并不表示它不起作用。

从电话这个产品的角度看:一个电话生产过程中,电话的研发人员,需要关心这个电话的主机内部的电路是如何设计的,放大器怎么设置,电路板怎么设计,这些细节都要关心,一个环节出了问题,那就是一个无法正常使用的电话。但是,一旦这个电话在生产线上变成了成品,那么电话生产者是希望把听筒交给使用者,让他们用听筒能接打电话就好了,难道还会让他们自己拆卸电话主机改变里面的电路结构?听着耳熟是吧,没错!就是模块封装好了之后,对外就暴露一个有效接口就好了。

用JS代码实现这个场景

我从这个场景中抽象出两个行为:

  • 拿起电话(pickUpPhone)
  • 通过听筒说话(speakByReceiver)

再抽象出电话的几个部分:

  • 主机(Host)
  • 听筒(Receiver)
  • 连线(Chain)

它们的代码实现大概是这个样子的:

# pickUpPhone.js
function pickUpPhone() {

  //The definition of Host is hidden in phone without being exposed to outside
  function Host(){
    this.transform = function(){
      console.log('I am a host for transforming sound to digital.')
    }
  }
  let host = new Host()

  //The receiver has to collaborate with a host
  function speakByReceiver(){
    console.log('You are speaking by a receiver depending on the host.')
    host.transform()
  }

  //The phone only exposes speakByReceiver to outside but it need the host's help
  return speakByReceiver
}
复制代码
# main.js
let speakByReceiver = pickUpPhone()
speakByReceiver()
复制代码

和JS语法无关的代码解释

我试图用通俗易懂的方式,来解释这段代码,从而引出闭包的涵义。所以,代码中省略了模块输出和输入(它们是干扰项),我在本机上模拟时,是用CommonJS导出和导入模块的,在node8上运行的。但是这都不重要,即使我们根本不知道这些,一点也不耽误我们理解接下来我要说的。

在一般的印象中,当一个函数运行结束后,该函数作用域内的局部变量,都会释放掉。所以,站在 pickUpPhone() 的角度看,当它执行结束后,它的局部变量 host 就释放掉了,所以 host 应该是 undefined 。但是,实际上,并非总是如此。在这个例子中, main.js 中的代码运行结果如下:

You are speaking by a receiver depending on the host.
I am a host for transforming sound to digital.
复制代码

这说明 host 并没有随着 pickUpPhone() 的执行完成而消失。

站在使用者 main.js 的角度看,它根本不关心 speakByReceiver() 是怎么实现的,它就想调用一下这个函数就好了,这个函数爱咋实现咋实现,和 main.js 半毛钱关系都没有。但是,不关心,不等于不存在。如果没有 host ,这个段代码就无法正确的执行(对比打电话,没有电话主机,打电话这事就没法完成)。

站在 speakByReceiver() 的角度看,这个函数定义的时候,所在的 context 中,有一个叫做 host 的外部变量,而该函数在执行的时候必须要使用它。看起来, pickUpPhone() 似乎有一种先知能力,它知道未来自己要用到一个不属于它自己的变量 host ,为了不让这个不属于它的变量,脱离它的掌控,它使用了一种类似于“锁链”的东西,将 host 牢牢锁在自己身边,不让它离开(释放)。

说到这,闭包的涵义就呼之欲出了。用白的不能再白的大白话说: 闭包就是一条锁链,它的一头是某函数的定义 context ,而领一头是该函数的执行 context ,这条锁链把本来不应该产生交集的两个 context 系到了一起,使得该函数在运行时,仍然能够毫无障碍的使用它被定义的时候,所在的 context 的变量

把生活场景和JS代码联系起来

生活场景讲完了,JS代码也讲完了,完成了一个从生活到技术的简单映射。下面,我们通过这幅图,把两者联系起来看

没学过JavaScript也能看懂的闭包解释
从这幅图上,直观的不能再直观的告诉你了: 那根弯弯的线,就是闭包啊

那这根细细的线有什么好处呢?主要的好处就是把电话主机和听筒连起来了,既让我们可以通过听筒完成打电话这个事情,又让我们在不关注主机如何工作的情况下,成功的把语音转化为电信号给传了出去。

那闭包的好处是什么呢?主要的好处和电话线一模一样:

一个JS模块只对外暴露部分接口,而隐藏细节实现,当被暴露出去的接口被调用时,又可以毫无顾忌使用模块中的任何资源,并且这些资源又不会散落在外,而污染全局。

我的天啊,怎么还有这样的美事呢?这显然就是又让贼吃肉,还不用挨打的典范啊。等等!你说什么?大点声!闭包会引起内存泄露?

闭包会引起内存泄露么?

有部分同学是持有这个观点的,至少在我面试的人中,就有同学对我说:闭包会引起内存泄露。现在,就让我们彻底分析一下,到底闭包和内存泄露有没有关系。

首先,我们不考虑神马技术问题,让我们还是回到打电话这个场景中。如果上面打电话的那位帅哥打完电话之后,并没有把听筒撂下,而是拿着听筒到处走,会发生什么事情?显然,听筒连着电话主机,会把让整个电话,通过听筒都挂在他身上,只要把不撂下电话,那么他就一直带着这个电话。上班,下班,约会,睡觉,洗澡,都会带着这个电话。如果这件事情真的发生了:

  • 人们会投诉这个电话的生产厂商么?说,你这个电话设计的有问题,严重影响了别人的生活,打过电话后,电话会一直挂在身上,下不来,增加了个人的体重,让人更加的臃肿了。

  • 亦或是,为了不让电话影响到正常的生活,而一刀子剪断听筒和主机之间的连线。

显然,只要脑子还没坏掉,就绝对不可能做以上两件事情。我们只需要轻轻的把听筒放下就好了。

然后,让我们回到技术层面。一个封装好的JS模块,暴露给外界部分函数,而这些函数的执行又依赖于模块内部的资源,这个模块本身,完全没有问题。实际上,在没有CommonJS, AMD, ES6 Module之前,模块封装就是利用JS函数来完成的。一个函数内定义若干局部变量和内部函数,然后把一部分暴露给外界,这就是一个JS的独立模块。

而对于函数式编程支持特别好的语言,都是支持闭包的。因为只有闭包,才能在不污染全局的前提下,为被执行的函数提供运行所需的,又并非来自参变量的数据。

所以,假设上面的代码只是整个程序的一小部分,当 speakByReceiver() 执行完后,程序并没有退出,那么只需要加上一句,就可以彻底杜绝内存泄露的隐患。

speakByReceiver = null
复制代码

所以,不正确的使用闭包才会造成内存泄露,正确利用闭包,是不存在内存泄露的。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大教堂与集市

大教堂与集市

[美] Eric S. Raymond / 卫剑钒 / 机械工业出版社 / 2014-5 / 59.00元

当代软件技术领域最重要的著作,中文版首次出版! 《大教堂与集市》是开源运动的《圣经》,颠覆了传统的软件开发思路,影响了整个软件开发领域。作者Eric S. Raymond是开源运动的旗手、黑客文化第一理论家,他讲述了开源运动中惊心动魄的故事,提出了大量充满智慧的观念和经过检验的知识,给所有软件开发人员带来启迪。本书囊括了作者最著名的“五部曲”,并经过作者的全面更新,增加了大量注释,提高了可读......一起来看看 《大教堂与集市》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具