之前学习闭包的时候一直处于一知半解的状态,今天重新把闭包的知识点理一理
1. 什么是闭包
简单的说,闭包就是函数中嵌套一个内部函数,而这个内部函数可以引用外部函数的参数和变量,这些被内部函数引用的外部函数的参数和变量不会被垃圾回收机制所回收。我们可以先看一个简单的例子:
function aaa(){
var a = 1;
a++;
console.log(a);
}
aaa(); // 2
aaa(); // 2
上面程序调用了两次aaa()函数,但是两次的输出结果都是2,也就是说在第二次执行的时候,函数又重新给变量a赋值,如果我们要在内存中保留变量a的值,使得接下来对a的引用都是在原来的基础上,那么上述的代码可以改写成如下:
function aaa(){
var a = 1;
return function(){
a++;
console.log(a);
};
}
var b = aaa();
b(); // 2
b(); // 3
上述代码对b()函数进行了两次调用,分别输出了2和3,也就说,第一次调用结束后,垃圾回收机制并没有对参数进行回收,该参数在调用结束后仍然停留在内存中,因此在第二次调用的时候直接在第一次调用的基础上对变量a进行累加。这就是一个闭包形式。
2. 闭包的作用
(1) 希望一个变量长期留在内存当中,以便后期对其进行引用 (该特点在上述已经介绍过,因此不另做详细说明)
(2)避免全局变量的污染
先来看一个例子,如果我们需要对一个变量每次调用都累加一次,也就是实现1中程序2的功能,通常我们可以写成如下形式:
var a = 1;
function aaa(){
a++;
console.log(a);
}
aaa();
aaa();
上述代码同样能够实现每次调用都对a进行累加的功能,但是a是全局变量,为了提高性能,我们应该尽量避免全局变量。因此,建议使用闭包来实现该功能。
(3)模块化代码,对函数内成员私有化
对(2)中功能用闭包形式实现,并将其模块化可写成如下:
var aaa = (function(){
var a = 1;
return function(){
a++;
console.log(a);
};
})();
aaa();
aaa();
这样,aaa就是外部内名函数的返回结果,该返回结果也是一个内部的匿名函数,因此,我们可以直接对aaa进行调用了。下面再介绍一个模块化代码。
var aaa = (function(){
var b = 1;
var c = 2;
function bbb(){
b++;
console.log(b);
}
function ccc(){
c+=2;
console.log(c);
}
return {
bb: bbb,
cc: ccc
};
})();
aaa.bb(); //2
aaa.bb(); //3
aaa.cc(); //4
aaa.cc(); //6
通过模块化封装后,不能直接获取函数内成员,只能通过函数来获取。这样就对函数内部的成员起到了保护作用,也增加了代码的复用性。
(4)在循环中直接找到对应元素的索引值
需求描述:创建一个ul列表,里面存在多个li,当我们在点击某个li时,弹出对应的索引号。通过我们的做法是:
// html代码
<ul>
<li>111111111111</li>
<li>222222222222</li>
<li>333333333333</li>
<li>444444444444</li>
</ul>
// JavaScript代码
window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
aLi[i].onclick = function(){
alert(i);
};
}
};
运行之后我们会发现,不管我们点击哪个li,弹出来的都是4。这是因为,我们当点击事件还没有发生时,for循环就已经结束,这时候的i的就已经为4了,所以不论我们点击哪一个li,出来的都是4,这时我们可以利用闭包来实现上述的需求。
window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
aLi[i].onclick = (function(i){
return function(){
alert(i);
};
})(i);
}
};
当然,如果不用闭包,通过遍历li也是能达到相同的效果的:
window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
aLi[i].index = i;
aLi[i].onclick = function(){
alert(this.index);
};
}
};
3. 使用闭包时需要注意的问题
(1)闭包会引起的最大问题就是内存泄漏问题,由于闭包会使得函数中的变量一直保存在内存中,因此内存消耗很大,不能滥用闭包。解决办法就是在退出函数之前,将不使用的局部变量全部删除。
(2)闭包引起的另外一个问题就是函数内部变量值会在函数外部被闭包改变,这点也是需要注意的。
4. 思考题
之前笔试时碰到一个关于闭包的题目:问下面程序运行的结果
function Foo(){
var i = 0;
return function(){alert(i++);};
}
var f1 = Foo(),f2=Foo();
f1(); //0
f1(); //1
f2(); //0
调用f1()时,先弹出i的值为0,然后i的值再加1,然后再调用f1(),此时弹出第一运行后i的值,所以弹出1,然后再讲i值加1;然后调用f2(),由于f1()和f2()是不同的函数,所以两者运行时不影响,最后弹出的值0。