闭包概念的深入理解

MDN上面对于闭包的定义:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

说的直白一点就是闭包不仅仅只是函数本身,也包括一切与它相连的变量的一个闭包体。ES6的let语法出现之前,JS语言是没有块级作用域的,只有全局作用域和函数作用域。而其他语言例如JAVA和PHP都有。所谓块级作用域,比如代码块for循环或if等等。

先看一下下面这段代码:

// 父级函数foo
function foo(x) {
    var z = 3 // 父级函数内部的变量z
    // 返回一个匿名子函数
    return function (y) {
        // 子函数内部可以访问的变量有自己作用域的参数y和父级函数内定义的变量z和父级函数的参数变量x,还有最外层的全局变量bar
        console.log(x + y + z)
    }
}
var bar = foo(2) //
bar(3)

我们来慢慢解释一下上面的代码:
首先我们定义一个函数foo,然后在里面定义一个变量z,然后返回一个匿名函数。这个匿名函数就是foo的子函数。
然后在最外面执行foo这个函数并把返回值,也就是里面的那个匿名子函数给到变量bar
最后执行bar这个函数,参数3就是y

先看一下匿名子函数里面能访问的变量一共有哪些。
因为是最内层,所以子函数内部可以访问的变量有自己作用域的参数y和父级函数内定义的变量z和父级函数的参数变量x,还有最外层的全局变量bar

像这种情况,赋值到bar变量的那个匿名的子函数就是一个闭包体。它的词法环境就包括了函数本身和外层的z变量和x参数变量。

接下来说一下JS的回收机制。
当一个函数执行完毕之后就会把它和它里面定义的变量一同销毁。比如上面的foo函数执行之后里面定义的z变量就会被销毁。
但是由于我们已经把闭包体赋值给了bar这个变量,所以就算执行foo(2),里面的变量z也不会被销毁。因为闭包体被变量bar引用到,除非把变量bar也销毁,z才会被释放。这就是闭包会引起内存泄漏的原因。

闭包的缺点就是会引起内存泄漏。那么闭包有什么用?
最常见的用途就是函数柯里化,请参考我的另一篇文章,专门介绍什么是函数柯里化。