剥离它天生的骄傲,还原闭包的本质。
代码中的闭包
先不去解释闭包这个名词,看一段JS代码:
访问函数中的变量
基本常识,你无法直接访问函数体中的变量,如果你知道JS中的作用域规则的话。
我们修改代码后,再次访问函数中的变量
这看上去没有什么新的内容,如果你了解JS的一些基本概念和语法,这再简单不过了,但是,这里面第二段代码里面就已经有了闭包,你只是不知道而已,实际上就算你不知道闭包,仍然可以去写代码,完成你的需求,而不是看到一个需求,想:我要用闭包。你按照需求写代码,就能够写出闭包,虽然你不知道自己写的就是闭包,那么第二段代码中的闭包是什么呢?
代码中的闭包
name 和 函数 returnName 就构成了一个闭包。好了,你到目前为止不需要知道什么是闭包,不需要知道为什么上面两个变量就构成了闭包,你只需要知道和理解的是,这两段代码的执行原理和执行结果就可以了。如果不知道,就关掉网页,重头学。
闭包的定义
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。—— 维基百科
函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。—— MDN
我个人觉得这些解释很好,但如果你是刚刚接触JS,那么就不太接受,下面我用自己的理解说一下对闭包的理解。 你需要知道的几个前置知识:
- JS的作用域及作用域链
- 函数作用域
- 变量的生命周期
在上面的两段代码中,第一段代码函数外部是无法访问函数内部的变量的(var定义的),要想访问到这个变量,那么需要在函数中再定义一个函数,这样,这个“子函数”就能够访问到“父函数”里面的变量了。为了能够在外面使用到这个函数,就需要将这个“子函数”作为“父函数”的返回值return 出来给外面使用。这样就构成了一个闭包,不要急,你或许会迷糊,这和没解释一样啊,这些我都知道啊,是的,你不要怀疑你自己,你已经知道了什么是闭包,但你不知道那就是闭包。(哈哈哈)。
可以给闭包一个最简洁的定义:
闭包 = 函数 + 被引用的自由变量
在第二段代码中,函数是 returnName ,这个函数引用的自由变量是 name。
其实,你只要理解了作用域和作用域链,就能够理解闭包,只是,你不会也不需要刻意写闭包而已。
闭包的作用
1.访问函数内部的变量
其实很多所谓的闭包的许多作用只是这两个的延申,比如模拟私有方法,你按照自己的需求写出了代码,只不过写的“恰巧”实现了闭包。
2.延长变量的生命周期
变量一般在其环境消失后,变量随之销毁,无法访问到了。所以当函数执行完毕后,函数里面的变量就会随之销毁。函数被调用时,才会产生一个执行上下文(环境), 但是如果该变量被引用了,则该变量就不会被销毁,闭包就是引用了外部的变量的函数,这样这个外部变量就不会被销毁。
(以下代码及文字摘自阮一峰博客) 看代码:
result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
一些注意的点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
特别要注意的是,闭包不会导致内存泄漏的问题,那是IE浏览器的问题,不是闭包的问题。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
一些废话
在刚刚开始学习JavaScript的时候,会碰到各种技术名词,其中“闭包”应该算是最最响亮的一个了,我可能是完美主义,学习技术的时候总会力求“全面”,会研究每一个技术名词,概念,以为只有这样才算掌握了知识, 之到我接触到“闭包”后,我发现我很多学习方法都是不科学的,因为技术概念和名词都是技术人对某种现象,某些特性的一共归纳总结,是马后炮式的归纳总结,如果我总是去想搞清楚这些概念到底是什么意思,是没有必要的,很多时候,其实你已经掌握了,但你自己未必知道,闭包就是这样一个名词。忘记各种名词和概念,学知识本身,而不是死抠概念,这是我的一点感悟。