闭包是一个难懂但是又必须要理解征服的概念。闭包的形成与变量的作用域和变量的生存周期息息相关。
1、变量的作用域(作用范围)
变量的作用域就是指变量的有效范围,变量的作用域是由声明变量的位置决定的,声明变量的同时也就指明了该变量的作用范围,最常讲的就是函数中变量的作用域。如果在函数中声明一个变量没有使用关键字var,这个变量就会成为全局变量,很容易造成命名冲突。
var c = 3;//全局变量
var fn = function(){
var a = 1;//局部变量
b = 2;//没有使用 var关键字 所以是全局变量
console.log(a,b,c);
}
fn();//输出 1,2,3
console.log(b);//输出 2 也可以这么访问 window.b
console.log(window.c);//输出 3
console.log(a);//Uncaught ReferenceError: a is not defined
可以看出来没有使用var关键字的b成了全局变量,而变量a(局部变量)在函数外部访问不到,而c是全局变量,在函数fn内部当然可以访问的到。
函数可以用来创建函数作用域,函数里面可以访问到函数外面的变量,但是在函数外面却访问不到函数内部的变量。因为当在函数中访问一个变量的时候,如果该函数内部并没有声明该变量,那么会沿着代码执行环境创建的作用域链向外逐一查询,直到搜索到全局对象为止(变量的查询是从内到外的)。
2、变量的生存周期
全局变量生存周期是永久的,除非我们主动销毁这个全局变量。而函数内部使用var关键字声明的局部变量来说,当函数执行完毕之后,这些局部变量就失去价值了,它们随着函数的调用的结束将会被销毁。
var fn = function(){
var a = 1;
console.log(a);
return function(){
a++;
console.log(a);
}
}
var f = fn();//输出 1
f();//输出 2
f();// 输出 3
按照上所述我们在执行fn函数以后fn函数内部声明的局部变量a应该被销毁,但是结果却不是这样的,局部变量a并没有被销毁,好像a一直存在。主要是因为我们执行完fn函数以后,它又返回了一个匿名函数引用,该匿名函数可以访问到fn函数被调用时创建的作用域链,然后沿着该作用域链查找变量a,而局部变量a仍处在该环境中。所以局部变量所在的环境还能被外界函数(f函数)访问到,那么局部变量a就不应该被销毁,这就产生了闭包。闭包延长了局部变量a的生存周期。
3、闭包典型示例
var arr = [];
for(var i = 0;i<5;i++){
arr[i] = function(){
console.log(i);
};
}
arr[0]();//输出 5
arr[1]();//输出 5
arr[2]();//输出 5
arr[3]();//输出 5
arr[4]();//输出 5
我们想要的输出结果是 0,1,2,3,4 但是真正结果全部都是5。为什么?
我们执行arr数组中的函数,这些函数都要访问到变量i,它就会沿着作用链逐层查找,for循环是创建不了作用域,所以找到全局作用域中的变量i。在执行推出for循环之前执行了一次i++;所以此时的i的值为5。所以arr中的函数执行结果全是5。
要想实现我们想要的输出结果,就可以用闭包来解决
var arr = [];
for(var i = 0;i<5;i++){
arr[i] = (function(i){
return function(){
console.log(i);
};
})(i);
}
arr[0]();//输出 0
arr[1]();//输出 1
arr[2]();//输出 2
arr[3]();//输出 3
arr[4]();//输出 4
将每次for循环的i值作为参数传入到立即执行匿名函数中封闭起来,该立即执行匿名返回一个函数,该函数保存着立即执行函数中封闭的参数。所以当我们调用arr中函数时,会顺着作用域链从内到外查找变量i,首先会找到在闭包中封闭的变量i,所以输出我们想要的结果。
由于闭包会使得函数中的变量都被保存在内存中(如2中的a变量),这样内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE浏览器中可能导致内存泄露。我们可以在退出函数之前,将不使用的局部变量全部删除。
但是如果闭包使用的恰当合适的话,可以帮助我们减少代码数量和代码复杂性,可以大大简化那些复杂的操作。
如果有写的不对的,或者理解不到位地方,欢迎各位指正。