灏天阁

闭包

· 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 的值都变为了 3tab[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);
    }
}

- Book Lists -