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才会被释放。这就是闭包会引起内存泄漏的原因。
闭包的缺点就是会引起内存泄漏。那么闭包有什么用?
最常见的用途就是函数柯里化,请参考我的另一篇文章,专门介绍什么是函数柯里化。