什么是闭包?
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
JS中,在函数内部可以读取函数外部的变量
function outer(){
var num = 10;
return num;
}
outer();//10
但在函数外部无法读取函数内部的局部变量
function outer(){
var num = 10;
}
console.log(num);//error
这里有两个需要注意的地方
- 在函数内部声明变量时,必须使用var命令,否则就相当于声明了一个全局变量。
- 全局变量从创建的那一刻起就会一直保存在内存中,除非你关闭这个页面;局部变量当函数运行完以后就会销毁这个变量,假如有多次调用这个函数它下一次调用的时候又会重新创建那个变量,既运行完就销毁,回到最初的状态,简单来说局部变量是一次性的,用完就扔,下次需要再重新创建。
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
function outer(){
var num = 30;
function inner(){
console.log(num);
}
return inner;
}
var fun = outer();
fun();//30
在上述代码中,outer函数内又定义了一个函数inner,outer函数的返回值是inner函数,inner函数把num 打印出来。
我们可以看出以上代码的特点:函数嵌套函数,内部函数可以引用外部函数的参数和变量,参数和变量不会被垃圾回收机制回收。
这就形成了闭包。简单来说,闭包就是能够读取其他函数内部变量的函数。
本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
在上面的代码中,函数inner被包含在函数outer内部,这时outer内部的所有局部变量,对inner都是可见的。但是inner内部的局部变量,对outer 是不可见的。这是JavaScript语言特有的“链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
敲重点!!
下面问题来了,有以下代码:
function a(){
var num = 1;
return function(){
console.log(num);
}
}
假设这个闭包函数为b
怎么调用b?
a的作用就是return一个b函数,那么我们可以写出这样的表达式:
var b = a();
这个b,就是这个b闭包函数的引用。
b的作用域链是什么?
在执行a时,会生成一个a函数的活动对象,那么在定义b的时候,作用域链里就会有三个对象的引用:最优先的是b函数本身的活动对象,其次是a函数的活动对象,再次是window。
b函数如何寻找变量?
它会依次在作用域链存放的三个活动对象里面去找,找到即返回,都没找到返回undefined。
函数执行完了,它的活动对象就销毁了,那b执行的时候,a已经执行完了,为什么还可以访问里面的值?
a的活动对象(存储了它内部的变量)也是对象,当b的作用域链还存在对它的引用时,无法销毁。也就是说只要b的引用存在 ,a的活动对象一直保留。
由此,可以总结一下闭包的优点和缺点。
闭包的优点:
- 希望一个变量长期存储在内存中。
- 避免全局变量的污染。
- 私有成员的存在。
闭包的缺点:
- 常驻内存,增加内存使用量。
- 使用不当会很容易造成内存泄露。
闭包的使用场景:
既然已经了解了闭包,接下来来看一下它的应用场景。
有以下一段代码
不使用闭包
function fun(){
var ul = document.getElementsByTagName("ul")[0];
var li = ul.getElementsByTagName("li");
for(var i = 0;i < li.length;i++){
li[i].onclick = function(){
console.log(i); //不管怎么点结果都返回6
}
}
}
fun();
使用闭包
function fun(){
var ul = document.getElementsByTagName("ul")[0];
var li = ul.getElementsByTagName("li");
for(var i = 0;i < li.length;i++){
(function(i){
li[i].onclick = function(){
console.log(i); //点击第几个返回相应的数字
}
})(i)
}
}
fun();