闭包
·
Yin灏
典型的闭包是一个嵌套结构的函数,内部函数引用外部函数的私有成员,同时内部函数又被外界引用,当外部函数被调用后,就形成了闭包。这个函数也称为闭包函数。
function f(x) {
return function(y) {
return x + y;
}
}
var c = f(5);
console.log(c(6)); // 11
闭包变体
除了嵌套函数外,如果外部引用函数内部的私有数组或对象,也容易形成闭包。
var add;
function f() {
var a = [1,2,3];
add = function(x) {
a[0] = x*x;
}
return a;
}
var c = f();
console.log(c); // [ 1, 2, 3 ]
add(5);
console.log(c); // [ 25, 2, 3 ]
add(10);
console.log(c); // [ 100, 2, 3 ]
使用闭包
使用闭包实现优雅的打包,定义存储器。
var f = function () {
var a = [];
return function (x) {
a.push(x);
return a;
}
}();
var a = f(1);
console.log(a); // ['1']
var b = f(2);
console.log(b); // ['1', '2']
/*
通过外部函数设计一个闭包,定义一个永久的存储器。当调用外部函数生成执行环境后,就可以利用返回的匿名函数不断地向闭包体内的数组 a 传入新值,传入的值会持续存在。
*/
在网页中事件处理函数很容易形成闭包。
<button onclick="f()">生成闭包</button>
<button onclick="b()">查看a的值</button>
<button onclick="c()">递增</button>
<button onclick="d()">递减</button>
function f() {
var a = 1;
b = function () {
console.log("a=" + a);
}
c = function () {
a++;
}
d = function() {
a--;
}
}
闭包的局限性
闭包的价值是方便在表达式运算过程中存储数据,但是缺点也很明显。
- 由于函数调用后,无法注销调用对象,会占用系统资源,在脚本中大量使用闭包,容易导致内存泄漏,非必要不要滥用。
- 由于闭包的作用,其保存的值是动态的,如果处理不当,容易出现异常或错误。
<div class="tab_wrap">
<ul class="tab" id="tab">
<li id="tab_1" class="hover">Tab1</li>
<li id="tab_2" class="hover">Tab2</li>
<li id="tab_3" class="hover">Tab3</li>
</ul>
<div class="content" id="content">
<div id="content_1" class="show">1</div>
<div id="content_2" class="none">2</div>
<div id="content_3" class="none">3</div>
</div>
</div>
window.onload = function () {
var tab = document.getElementById('tab').getElementsByTagName('li');
var content = document.getElementById('content').getElementsByTagName('div');
for (var i = 0; i < tab.length; i++) {
tab[i].addEventListener('mouseover', function() {
for (var n = 0; n < tab.length; n++) {
tab[n].className = 'normal';
content[n].className = 'none';
}
tab[i].className = 'hover';
content[i].className = 'show';
})
}
}
上面的代码会报错,在 load
事件处理函数中,使用 for
语句为每个 li
元素绑定 mouseover
事件,在 mouseover
事件处理函数中重置所有选项卡 li
的类样式,然后设置当前 li
选项卡高亮显示,同时显示对应的内容容器。
但是在浏览器中预览时,会发现浏览器抛出异常。
在 mouseover
事件处理函数中跟踪变量 i
的值,i
的值都变为了 3
,tab[3]
自然是一个 null
,所以也不能读取 className
属性。
- 原因分析:
上面 JS
代码是一个典型的嵌套函数结构,外部函数为 load
事件处理函数,内部函数为 mouseover
事件处理函数,变量 i
为外部函数私有变量。
通过事件绑定,mouseover
事件处理函数被外界引用(li
元素),这样就形成了一个闭包体。虽然在 for
语句中为每个选项卡 li
分别绑定事件处理函数,但是这个操作是动态的,因此 tab[i]
中 i
的值也是动态的,所有就会出现上述异常。
- 解决办法:
解决闭包的缺陷,最简单的方法是阻断内部函数对外部函数的变量引用,这样就形成不了闭包体。我们可以在内部函数(mouseover
事件处理函数)外边增加一层防火墙,不让其直接引用外部变量。
window.onload = function () {
var tab = document.getElementById('tab').getElementsByTagName('li');
var content = document.getElementById('content').getElementsByTagName('div');
for (var i = 0; i < tab.length; i++) {
(function (j) {
tab[j].addEventListener('mouseover', function () {
for (var n = 0; n < tab.length; n++) {
tab[n].className = 'normal';
content[n].className = 'none';
}
tab[j].className = 'hover';
content[j].className = 'show';
})
})(i);
}
}