JS闭包

本文探讨了JavaScript中闭包的形成原理及其应用场景,包括如何利用闭包延续局部变量的生命周期、封装私有变量以及解决事件监听器中的作用域问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

闭包的形成与变量的作用域以及变量的生存周期密切相关。

变量的生存周期

 对于全局变量来说,全局变量的生存周期当然是永久的,除非我们主动销毁这个变量。而对于在函数内用var关键字声明的局部量来说,当退出函数时,这些局部变量就失去了他们的价值。

var func = function () {
            var a = 1;   // 退出函数后局部变量a将被销毁
            alert(a);
        };
        func();
 现在来看看下面的代码:

var func = function () {
            var a = 1;
            return function () {
                a++;
                alert(a);
            }
        };
        var f = func();

        f();   // 2
        f();   // 3
        f();   // 4
        f();   // 5
 跟我们之前的推论相反,当退出函数后,局部变量a并没有消失,似乎一直在某个地方存活着。

这是因为当执行var f = func();时,f返回了一个匿名函数的引用它可以访问到func()被调用时产生的环境,

而局部变量a一直处在这个环境里。既然局部变量所在的环境还能被外界访问,这个局部变量就有了不被销毁的理由。

在这里产生了一个闭包结构,局部变量的生命看起来被延续了。

下面介绍一个闭包的经典应用。代码如下:

<body>

    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>

<script>
    var nodes = document.getElementsByTagName('div');

    for (var i = 0; i < nodes.length; i++){
        nodes[i].onclick = function () {
            alert(i);
        }
    }
</script>

</body>
测试代码发现,无论点击哪个div,弹出的都是5。这是因为div节点的onclick事件是被异步触发的,当事件被触发的时候,此时变量

i的值已经是5,所以在div的onclick事件函数中顺着作用域链从内到外查找变量i时,查到的总是5。

解决的方法是在闭包的帮助下,把每次循环的i值都封闭起来。当在事件函数中顺着作用域链中从内到外查找变量i时,会先找到被

封闭在闭包环境中的i,如果有5个div,这里i就分别是01234

for (var i = 0; i < nodes.length; i++){
        (function (i) {
            nodes[i].onclick = function () {
                alert(i);
            }
        })(i)
    }


闭包的更多作用
1.封装变量

闭包可以帮忙把一些不需要暴露在全局的变量封装成“私有变量”。假设有一个计算乘积的简单函数:

var mult = function () {
        var a = 1;
        for (var i = 0; i < arguments.length; i++){
            a = a * arguments[i];
        }
        return a;
    }
mult函数接受一些number类型的参数,并返回这些参数的乘积。现在我们觉得对于那些相同的参数来说,每次都进行计算是一种

浪费,可以加入缓存机制来提高这个函数的性能:

var cache = {};

    var mult = function () {
        var args = Array.prototype.join.call(arguments, ',');
        if (cache[args]){
            return cache[args];
        }
        var a = 1;
        for (var i = 0; i < arguments.length; i++){
            a = a * arguments[i];
        }
        return cache[args] = a;
    };

    alert( mult(1,2,3) );   // 6
    alert( mult(1,2,3) );   // 6
cache这个变量仅仅在mult函数中被使用,与其让cache变量跟mult函数一起平行的暴露在全局作用域下,不如把它封闭在mult

函数内部,这样可以减少页面中的全局变量,以避免这个变量在其他地方被不小心修改而引发错误。代码如下:

var mult = (function () {
        var cache = {};

        return function () {
            var args = Array.prototype.join.call(arguments, ',');
            if (cache[args]){
                return cache[args];
            }
            var a = 1;
            for (var i = 0; i < arguments.length; i++){
                a = a * arguments[i];
            }
            return cache[args] = a;
        }
    })();
提炼函数是代码重构中一种常见技巧。如果在一个大函数中有一些代码块能够独立出来,我们常常把这些代码块封装在独立的

小函数里,有助于代码的复用。如果这些小函数不需要在程序的其他地方使用,最好是把它们用闭包封装起来。代码如下:

var mult = (function () {
        var cache = {};

        var calculate = function () {   // 封闭calculate函数
            var a = 1;
            for (var i = 0; i < arguments.length; i++){
                a = a * arguments[i];
            }
            return a;
        };

        return function () {
            var args = Array.prototype.join.call(arguments, ',');
            if (args in cache){
                return cache[args];
            }
            return cache[args] = calculate.apply(null, arguments);
        }
    })();
2.延续局部变量的寿命

img对象经常用于数据上报,如下所示:

var report = function (src) {
        var img = new Image();
        img.src = src;
    };

    report('http://xxx.com/getUserInfo');
因为一些低版本浏览器的实现存在bug,在这些浏览器下使用report函数进行数据上报会丢失30%左右的数据,也就是说,

report函数并不是每一次都成功发起了http请求。丢失数据的原因是img是report函数中的局部变量,当report函数的调用结束后,

img局部变量随即被销毁,而此时或许还没来得及发出http请求。

现在把img变量用闭包封装起来,便能解决请求丢失的问题:

var report = (function () {
        var imgs = [];
        return function (src) {
            var img = new Image();
            imgs.push(img);
            img.src = src;
        }
    })();
闭包和面向对象设计
可以使用闭包来实现一个完整的面向对象系统。

var extent = function () {
        var value = 0;
        return {
            call: function () {
                value++;
                console.log(value);
            }
        }
    };

    var extent = extent();
    extent.call();   // 1
    extent.call();   // 2
    extent.call();   // 3
如果换成面向对象的写法,如下:

var extent = {
        value : 0,
        call: function () {
            this.value++;
            console.log(this.value);
        }
    };
    extent.call();
    extent.call();
    extent.call();
或者:

var Extent = function () {
        this.value = 0;
    };

    Extent.prototype.call = function () {
        this.value++;
        console.log(this.value);
    };

    var extent = new Extent();
    extent.call();
    extent.call();
    extent.call();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值