我不是软件科班出身,表述会有不严谨的地方,希望大家能够帮忙指正。
说闭包之前,这几个概念需要了解:公有变量,私有变量,作用域
公有变量:大家都能用的变量,最经典的代表就是浏览器中的全局对象window
私有变量:只有某段程序能用的变量,有以下场景
function fn() { var word = 'hello' console.log(word)}复制代码
以上场景中的word变量,只能在fn函数中使用,在fn函数外部是无法访问到的,认为是fn函数的私有变量
作用域:一个变量的有效的区域(空间)
比如window全局变量,到处都能用,它的作用域就是“全局作用域”;
fn中的word变量,有效区域只在fn函数内,它的作用域就是“函数作用域”。
Javascript中,es5版本之下最常用的作用域只有这两种:全局作用域和函数作用域
(eval作用域等不被推荐的用法,就不探讨了)
这里先说说简短的个人理解:利用函数作用域来保护私有变量,并且返回了对被保护变量的引用,这样的行为就是闭包。
闭包的典型例子如下
var greet = (function() { var word = 'hello' //word变量被保护在这个立即执行函数(IIFE)内部 return function greet() { //返回一个函数 console.log(word) //保持对word的引用 }})()greet() // 'hello'复制代码
为什么要有闭包?
假设有以下场景,你写了一段程序,这段程序实现了某个功能,它的代码量不小,为了实现这个功能,你写了很多工具函数,引用了许多相关的变量。
这些函数和变量是相互组合使用的,它们是一个功能块,也就是“模块”,这个功能模块,总不能把它写在全局环境下吧?
不对,还是可以写在全局环境下的,反正代码跑起来也没什么问题。
假设你定义了一个变量a,一个函数b,直接写在了全局环境下,假设这个模块实现了功能A
那么,将来的程序员(包括你自己),想要写另外一个功能B
为了实现这个功能B,你也想写一个变量a,函数b,这个时候你就尴尬了,你想起来实现功能A的时候已经用掉了这些命名,不能再使用a、b了
你想到c、d这两个名字,你用了c和d这两个名字,开始调试,结果发现,功能A罢工了!
你检查后发现,原来写功能A的时候也用了c d两个名字,功能B里的命名对功能A产生了干扰。
命名冲突的原因几乎不用解释,但要描述出来的话,就是“当前作用域下已存在同名变量”,而功能相互冲突的原因,也是因为两个功能写在同样的作用域下,要解决以上问题,就要从作用域入手。
这个时候我们应该意识到:一个模块应该有自己的作用域,来保证模块的正常运行,
JS除了全局作用域外,能用的只有函数作用域了,所以我们可以用函数作用域来保护这个模块,那么接下来就要用到闭包了,因为闭包本质上就是一个函数作用域
闭包的应用
JS除了全局作用域外,能用的只有函数作用域了,所以你想到用函数作用域来保护这个模块,
假设你使用功能A的时候,用的其实也就是一个函数fnA,
那么你可以在函数作用域中写好了所有流程后,把这个fnA 通过 return语句暴露到外界
有以下代码
function getA() { var a = 'xxx' var c, d function b() { // 做了某些不为人知的事情 } //... function fnA() { b() console.log(a) // 剩余的程序... } return fnA}var fnA = getA()复制代码
这么一来,你的功能A就有了属于自己的独立作用域,里面的a b c d 啊什么东西,都被保护在了getA函数作用域下
不需要担心getA函数执行之后,作用域会销毁的问题,js引擎在检测到私有变量的引用保持之后,会自动保留这个作用域,并且加上一个特别的名字Closure(闭包),也就是说,这样的写法,就是闭包
结论
以上就是闭包的运用,我自己对闭包的看法:利用函数作用域来保护私有变量,并且返回了对被保护变量的引用,这样的行为就是闭包
等开发者在想写一个新功能的时候,不再需要考虑与旧功能命名冲突的问题,也不再需要担心新功能是否会影响旧功能。
关于闭包的成因,深入了解的话,建议搜索 JS的垃圾回收机制 和 JS作用域链