背景:javascript中没有块级作用域的概念,只有函数拥有作用域。
在下面的例子里,要实现累加,counter必须定义在autoadd函数之外,作为一个全局变量
var counter=1;
function autoadd(){
counter++;
console.log(counter);
}
在上面这个累加函数中,变量counter仅仅作为一个计数器存在,定义为全局变量没有必要。并且在多人开发的项目上,过多的全局变量容易产生冲突。
为了解决这个问题,利用作用域链的特性可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,这就是闭包
function wrapper(){
var a=1;
function autoadd(){
a++;
console.log(a);
}
}
wrapper();
但autoadd在wrapper中只是一个函数声明,我们并不能访问到。为了使我们能访问的真正需要的函数,我们可以将这个函数返回
var x=(function(){
var a=1;
return function(){
a++;
console.log(a);
}
})();
x();//2
x();//3
x=null;//解除引用,等待垃圾回收
总结一下闭包的作用:使当前作用域总能访问外部作用域中的变量。由此可以说,只要是内部函数引用了外层包裹函数中的变量则形成了一个闭包。而返回内层函数只是闭包为了取得内层函数的一种常见形式,并不是必要条件,不要混淆概念!
闭包与分组操作符()
var f = function() {
var num = 0;
return function() {
return num += 1;
};
}
f()() // 1
f()() // 1
var fn = f();
fn()
// 1
fn()
// 2
fn = null //解除引用
在这里f()
表示执行f所引用的函数,即外层函数,返回内层函数,但此时内层函数还并未执行。在f()
后再加一个分组符,表示执行f()
所引用的函数,即内层函数。然而为什么不论执行多少次f()()
结果都是1,而写成var fn = f();
就可以呢?因为第一种写法外部对内层函数并没有引用,在执行完之后其内存就被回收了。而在第二种写法中,fn是对内层函数的引用,只要fn不被解除引用,计数器num就不会被销毁
闭包与内存
为什么在第一个例子中counter必须写在累加函数外才能实现功能?
如果写在函数内,因为autoadd在执行完后,counter再也没有被其他地方引用,于是内存被回收了,它的生命周期只在累加函数中。而在第一个例子中,由于counter是全局变量,只有在关闭浏览器之后才会被回收,所以才能实现累加的功能。
在使用闭包的例子中,因为a被内层函数引用,而x通过外层函数的return引用到了内层函数。因此,只要x一直引用内层函数,a就不会被销毁。
var x=(function(){
var a=1;
return function(){
a++;
console.log(a);
}
})();
x();//2
x();//3
x=null;//解除引用,等待垃圾回收
这样也带了新的问题,内存泄漏。如果在代码中使用了大量闭包,而使用结束后不消除引用,闭包就会一直存在内存当中,当超过了内存的存储量之后就会造成内存泄漏。因此闭包在使用完之后一定要解除引用
闭包的优缺点
优点:
1)使变量驻留在内存中(多了变缺点);
2)避免全局变量污染;
3)私有化变量;
缺点:
1)因为闭包会携带包含它的函数的作用域,所以比其他函数占用更多内存;
2)使用不当会造成内存泄漏;
闭包的应用
该文章的后半段
参考博文:
https://www.cnblogs.com/libin-1/p/6384055.html
https://segmentfault.com/a/1190000012872694