什么是闭包
我认为【闭包】是描述了函数内部的一种数据行为。查阅了一些网站对闭包的定义后发现虽有细微差别但是共同之处都认同函数读取其他函数内部变量时,或是内部的函数引用了外部的函数的变量时形成了闭包。
闭包是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。运行时,一旦外部的函数被执行,一个闭包就形成了,闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。
——Wiki百科《闭包 (计算机科学)》
函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。
——MDN《JavaScrip>闭包》
JavaScript 变量属于本地或全局作用域。全局变量能够通过闭包实现局部(私有)。
——W3school《JavaScript 闭包》
闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
——阮一峰《学习Javascript闭包(Closure)》
个人认为我们在实际应用过程中不必纠结于闭包的定义,而应该关注是否有 外部函数调用之后本应该被销毁的变量,因为被内部函数使用而使我们仍然可以访问外部函数的变量对象这一闭包的关键。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,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
,可以在函数外部对函数内部的局部变量进行操作。
闭包的作用
- 可以访问函数内部的变量。
- 使函数执行完毕后,函数内变量的值仍保存在内存中。
- 用闭包模拟私有方法 防止变量污染
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
使用闭包的注意事项
- 由于闭包会使得函数在执行完毕后变量依然被保存在内存中,会加大内存消耗,在处理速度和内存消耗方面对脚本性能具有负面影响,所以不是某些特定任务需要使用闭包,尽量不要创建闭包。
- 避免使用过多的闭包,可以用
let
关键词声明变量,每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。 - 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
参考资料
——Wiki百科《闭包 (计算机科学)》
——MDN《JavaScrip>闭包》
——W3school《JavaScript 闭包》
——菜鸟教程《JavaScript 闭包》
——阮一峰《学习Javascript闭包(Closure)》