前言
在面试过程中,闭包是常考的问题,在很多框架和库中也使用到了闭包,包括我们在平时写代码也或多或少使用到了闭包。
一、什么是闭包
闭包的概念描述很多,我比较认同《你不知道的JavaScript》这样描述:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
在正式理解闭包之前,我们需要清楚什么是作用域、词法作用域、内存回收机制
1.1、作用域
一言以蔽之,作用域就是一套规则,用于确定在何处以及如何查找变量(标识符)的规则,形象描述的话,可以认为它是一个封闭的空间,只允许在这个封闭的空间内进行一些操作,也将这个封闭空间称为私有作用域。
更多的参考本篇作用域以及作用域链
1.2、内存回收机制
内存回收机制就是不在用到的内存,我们系统就自动进行回收从而清理出空间供其他程序使用
更多的参考本篇JavaScript内存管理
二、讲解闭包
上面已经提到了闭包的概念,这里直接看一段代码:

这里可以很清晰的展示闭包:
- fn2的词法作用域能访问到fn中的作用域
- fn2当作一个值返回
- fn执行之后,将fn2的引用赋值给f
- 执行f,将输入变量count
在第一次执行f()的时候,根据作用域链的规则(底层作用域没有声明的变量,会向上一级找,找到就返回,没找到就一直找,直到window的变量,没有就返回undefined)是没有疑问的输出1,那第二次的过程是怎样的呢? 继续执行那个函数的返回的方法,还是count+=1;然后再输出count ,这里问题就来了,不应该继续向上寻找,找到count=0;然后输出1吗?
原因是因为f保存的是fn的执行结果,即fn2,所以count的值只声明了一次,那么第二次的count的值又是从哪里来的?
这是第一次执行f()后留下来的那个变量。为什么函数变量执行完后没有被释放?这就是垃圾回收机制(标记清除)处理的一个问题了。
由于再次执行f()的时候,再次引用了第一次fn()产生的变量count ,所以count没有被释放,第一次f(),count 的值为1,第二次执行f(),count的值再加1,自然就是2了。
所以说如果执行两次fn,那么输出就应该都是1。
闭包demo.png
三、再看一个例子

我们的预期结果是1~10,但是确输出10次11。这是因为setTimeout中的匿名函数执行的时候,for循环都已经结束了,for循环结束的条件是i大于10,所以当然是输出10次11。
究其原因:i是声明在全局作用中的,定时器中的匿名函数也是执行在全局作用域中,那当然是每次都输出11了。
使用闭包解决:我们可以让i在每次迭代的时候,都产生一个私有的作用域,在这个私有的作用域中保存当前i的值。

这样就达到我们的预期了。
四、闭包的应用
4.1、实现私有成员

4.2、保护命名空间,避免污染全局变量

上面这个用法,叫做函数柯里化,就是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术

注意事项: 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题
五、总结
理解闭包是迈向高级JS程序员的必经之路,理解了其解释和运行机制才能写出更为安全和优雅的代码。
代码地址
网友评论