关于JS的闭包

1,375 阅读5分钟

一、前言

闭包它在百度百科中的解释原话是这样的:闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

下面来一一解读这段话。

二、闭包是什么?

首先来直接回答这个问题:闭包是什么?

答案:闭包是一个函数,什么函数呢?一个定义在函数内部的函数。例如:

var add = (function () {
    var counter = 0;
    return function () {
        return counter += 1;
    }
})();

在上面的例子中,函数内部return的这个函数function () {return counter += 1;},它就是一个闭包。我们直接把add在控制台打印出来:

我们可以看到,在这里全局定义的这个变量add,它实际上是一个函数,但是这个函数却是在另一个函数内部创建的,这个就是定义在函数内部的函数

三、闭包有什么作用?

1、闭包可以访问到局部变量

即我们要解读这句话:能够读取其他函数内部变量的函数

我们知道,在JavaScript中,有全局变量和局部变量。顾名思义,全局变量它是公有的、共享的,程序内的所有函数都可以直接调用这个全局变量,即它的作用域是全局性的;而局部变量,在函数内部声明的变量,它是私有的,只在函数内部起作用,在该函数之外的地方是无法被调用的,它的作用域是局部性的。

function add() {
    var counter = 0;
    return counter += 1;
}
console.log(add()); // 1
console.log(add()); // 1
console.log(add()); // 1
// 本意是想输出 3, 但事与愿违,输出的都是 1 

以上代码将无法正确输出,每次调用add()函数,计数器都会设置为1。可以将它写成闭包的形式,使counter = 0只执行一次,并返回函数表达式,然后得到我们想要输出的结果。

function add() {
    var counter = 0;
    return function () {
        return counter += 1;
    }
}
var closure = add();
console.log(closure()); // 1
console.log(closure()); // 2
console.log(closure()); // 3

闭包它可以访问函数上一层作用域的局部变量,它使得函数拥有私有变量变成可能。

2、闭包可以保护里面的局部变量,使它们不会随着函数的结束而销毁

百度百科里面还有这么一段话:“闭包”一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。

这句话含有一个意思,就是在闭包内部的变量它是不会被销毁的,会一直存在内存中,并且不会受到外界的干扰。在上面的例子中counter的值就是这样受到闭包的保护,只能通过closure方法去修改。

四、闭包的应用

闭包有两个作用,一个是可以访问到局部变量,另一个就是让这些变量的值始终保持在内存中。我们可以用闭包来解决一个经典的问题:

for (var i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 10)
}

因为setTimeout是个异步函数,所以会先把循环全部执行完毕,这时候i就是6 了,所以会输出一堆6。这个时候我们就可以用闭包去解决这个问题啦:

for (var i = 1; i <= 5; i++) {
    (function(j) {
        setTimeout(function timer() {
            console.log(j);
        }, 10)
    })(i)
}

在上面的代码中,我们首先使用了立即执行函数将i传入函数内部,这个时候值就被固定在了参数j 上面不会改变,当下次执行timer这个闭包的时候,就可以使用外部函数的变量j,从而达到目的。

当然,解决这个问题还可以用let和传入setTimeout的第三个参数,在这里就不一一展开描述了。

闭包因为会把变量保存在内存中,可以用来生成一个计时器,也就是文章开始的这个例子。

var add = (function () {
    var counter = 0;
    return function () {
        return counter += 1;
    }
})();

五、使用闭包需要注意的点

1、闭包会使得函数中的变量都被保存在内存中,会增大内存的使用量,所以不能滥用闭包,否则会造成网页的性能问题,在IE9中可能会导致内存泄露。但这个导致内存泄露是IE早期的BUG,闭包本身并不会导致内存泄露

因为,在JavaScript的垃圾自动回收机制中有这么一句话:从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。因为进入闭包的变量是有可能继续使用的,即它们不会被自动回收,这个时候需要我们手动回收清除。

2、闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。

最后,推荐观看下面关于闭包的这篇文章:

developer.mozilla.org/zh-CN/docs/…