【转】我也来说说啥是闭包,本来很简单的事,别人怎么都讲那么复杂

题外话:这篇文章也是前端网童鞋写的,可是里面的内容有些不好读,所以我自作主张排版了一下
为啥js中有闭包这个东西,其他后台语言里没有?
当一个函数调用时,内部变量的查找会按作用域链条来查找,按理说不会有啥特殊情况出现,js之所以会出现闭包这个现象,原因就是你调用的这个函数是另外一个函数的返回值。
说到函数能作为返回值,这是跟js中函数类型是第一类对象这种语言设计方式有关,我会先介绍第一类对象对js的编码风格的影响。当然你直接可以看第二部分,关于闭包的说明。

第一部分

啥是第一类对象呢,我帮你百度了。
第一类对象不一定是面向对象程序设计所指的物件,而可以指任何程序中的实体。一般第一类对象所特有的特性为:
  • 可以被存入变量或其他结构
  • 可以被作为参数传递给其他函数
  • 可以被作为函数的返回值
  • 可以在执行期创造,而无需完全在设计期全部写出
  • 即使没有被系结至某一名称,也可以存在
在js中object类型就是第一类对象。你能怎么使用object类型。就怎么能使用function类型。这里先说一下js中函数的四大作用
  1. 可以调用执行。程序世界里,函数的基本作用就是可复用代码段的封装,可以直接调用。
  2. 可以按照对象的使用方式来使用。原因就是js中函数类型是第一类对象。准确的来说js中函数本身就是对象。对象能做啥,他当然也能做。
  3. 可以提供作用域。js中没有块级作用域的概念。函数是提供作用域的最小单位。
  4. 可以作为构造函数。这一作用算是函数的特殊作用。可以作用生成其他对象的模板。也就是可以通过函数来模拟类的实现。
在讲闭包之前,先大致对函数的使用方式与对象和数组(数组本来就是对象,当然函数也是)做个对比。
1.关于字面量

1.1对象的字面量"{}"

1.2数组的字面量"[]"

PS:上面所有a 都是对象的引用

2.关于匿名函数
同样也有匿名对象和匿名数组,我们先看看他们是怎么使用的
3.可以存进变量或者其他结构。
因为数组元素中可以存入数组,当然也可以存入函数。对象也是,键值对的值可以存入任何东西,当然也可以存入函数,这时我们一般都用匿名函数,例如
4.可以做为参数,传入函数也就是平常我们说的回调函数。
众所周知函数有参数和返回值对象和数组作为参数没得说,写下函数相关的例子
5.作为返回值
对象和数组作为函数的返回值没得说,写下函数相关的例子

第二部分

现在还是说说为啥出了个闭包这个东西,原因就是你调用的那个函数是另一个函数的返回值,当外部调用时,会沿着这个返回值的函数作用域链条来找其内部相关变量的。
先大致说下作用域链条的问题。函数中识别变量,是一层层向外找的,首先在函数内部找,看看是不是内部声明的,然后再到上一层找,没找到,再往上,直到全局作用域。如果全局页面都没声明,那浏览器就报错了。这一层层中的层是什么东西呢,就是函数,因为函数提供最小的作用域。看个例子
作用域链条我们明白了,然后咱再来看看闭包的情形

为啥打印2而不是1呢,原因是因为a中返回个函数,我们要调用这个函数,浏览器一看,你要运行的是函数,函数是有作用域链条的,哦,x我能找到,保证不报错的。里面的x当然也能自增加了,说的直白点就像如下代码一样:

补充:经网友提醒,闭包有占用内存的问题,这里说下,因为代码1中fun是一个函数的引用,浏览器对应的会对其作用域链条中的变量x做了保存,因而会占用内存。达到的效果就跟代码2中的x一样。
要释放其内存可以把其引用置空,使a返回的那个匿名函数无引用指向它,自然垃圾回收器会回收的。代码如下

诶,为啥第二次不打印2了呢。原因很简单,因为两次调用返回的不是同一个函数引用,因此是两条作用域链条。
说的直白点就像如下的代码

这种使用方式,就不会有出现闭包常驻内存的情况,因为每次使用都匿名的,当然了,也失去了闭包的意义。

大体闭包这种现象我是解释明白了。我没有给闭包下明确的定义,不同的书有不同的说法。
有的说,返回的那个函数是闭包,有的说返回的函数提供的作用域链条是闭包。有的甚至把其得到效果说是闭包,
大体是这么说的,通过这种方式,能访问某个部函数内部的私有变量,这种方式称为闭包。
不管怎么说都是跟函数的作用域链条相关的。更有甚者也有说所有函数都是闭包。
我个人觉得会出现闭包这个东西,主要原因就是跟js中函数是第一类对象有关,因为你调用的一个函数可能不是直接声明的,而是其他函数直接return的函数或者return某种结构中的一个函数。
关于是返回某种结构的中函数,举例如下:

如果在讲上述例子改写新的形式,把函数改成匿名的(有的人甚至觉得匿名函数是闭包,那样我会说,看来所有函数都是闭包了),就是一种设计模式:模块模式。

第三部分

最后再来看看,如何避免闭包。
感谢TinyFish同学,ta当时的提醒并没有引起我重视。准确的说,下面也是一种闭包的应用。
原先是闭包,改后也也是闭包。达到的效果是,添加一层隔绝,来满足我们需要的结果。改于2016、3、8。
有时我们本意不想用闭包的,

如下,我想弹出0,1,2的,结果都会弹出3.

代码改写如下:

最开始的那个例子也可以避免闭包,改成

注意:经网友指正,原文中的代码如下,是有问题的,原因就是第二个return,又创建了闭包。代码敲顺手了,还得细心验证才行。
此处要改成,“方式二”

还有一种情况也会出现闭包现象,把内部函数绑定了dom节点某种操作(onclick)的回调函数,没有写在return语句里。道理是一样的。写在return里,是return后调用,绑定到dom上,比如说触发点击事件后再调用,其道理是一样的,作用域链条该怎么找就怎么找。
最后再说一句,闭包最起码的应用,就是我们可以把一些全局变量封装起来,通过这种方式来不污染全局,例如上面的模块模式例子。
评论 ( 0 )
最新评论
暂无评论

赶紧努力消灭 0 回复