什么是闭包?

注:不是数学里的闭包。
关注者
1530
被浏览
101913

92 个回答

JavaScript 闭包的本质源自两点,词法作用域和函数当作值传递。

词法作用域,就是,按照代码书写时的样子,内部函数可以访问函数外面的变量。引擎通过数据结构和算法表示一个函数,使得在代码解释执行时按照词法作用域的规则,可以访问外围的变量,这些变量就登记在相应的数据结构中。

函数当作值传递,即所谓的first class对象。就是可以把函数当作一个值来赋值,当作参数传给别的函数,也可以把函数当作一个值 return。一个函数被当作值返回时,也就相当于返回了一个通道,这个通道可以访问这个函数词法作用域中的变量,即函数所需要的数据结构保存了下来,数据结构中的值在外层函数执行时创建,外层函数执行完毕时理因销毁,但由于内部函数作为值返回出去,这些值得以保存下来。而且无法直接访问,必须通过返回的函数。这也就是私有性。

本来执行过程和词法作用域是封闭的,这种返回的函数就好比是一个虫洞,开了挂。也就是 @秋月凉 答案中轮子哥那句话的意思。

显然,闭包的形成很简单,在执行过程完毕后,返回函数,或者将函数得以保留下来,即形成闭包。实际上在 JavaScript 代码中闭包不要太常见。

函数作为第一等对象之后 JavaScript 灵活得不要不要的。
闭包其实是一个很通用的概念,正如 @寸志 老师说的:「闭包是词法作用域的体现」。 而很多大家耳熟能详的语言里都支持函数作为一类对象,比如JavaScript,Ruby,Python,C#,Scala,Java8.....,而这些语言里无一例外的都提供了闭包的特性,因为闭包可以大大的增强函数的处理能力,函数可以作为一类对象的这一优点才能更好的发挥出来。

那什么是闭包呢,一言以蔽之:一个持有外部环境变量的函数就是闭包。

理解闭包通常有着以下几个关键点:
1.函数
2.自由变量
3.环境

举个栗子:
let a = 1
let b = function(){
    console.log(a)
}
在这个例子里函数b因为捕获了外部作用域(环境)中的变量a,因此形成了闭包。 而由于变量a并不属于函数b,所以在概念里被称之为「自由变量」。
再举个栗子:
function a(x, y){
    console.log(x, y) //在这里,x和y都不是自由变量
    function b(){
        console.log(x, y) //但在这个内部函数b中,x和y相对于b都是自由变量,而函数a的作用域则是环境。
    }
    //无论b最终是否会作为返回值被函数a返回,b本身都已经形成了闭包。
}

为了更直观的描述「捕获」这个过程,再用PHP来个栗子:
function getMoney() {
  $rmb = 1;
  $func = function() use ( $rmb ) {
    echo $rmb;
  };
  $func();
}
在这里 use 这个关键字直接对应的描述了「捕获」这一概念。在代码中,开发者需要显式的通知引擎:我需要捕获一个环境里的变量在之后的Function Block里使用。(当然,由于PHP需要手动声明,所以经常被人诟病说支持的不够自然。

P.S:在一些语言里,「捕获」这一行为还有一个特点:闭包只会捕获自由变量的引用。
这也是为什么大家在JS里写循环的事件绑定时,需要再额外的使用一个IIFE来包裹的原因。(用IIFE对自由变量的引用立即求值)

所以其实闭包是一个比较直观的概念,不过之前有人问:如果这么解释,那和别的一些OO的语言看起来没区别啊,即使是早期的C#也可以像下面这么用,不也使用了函数作用域以外的变量吗

Class A
{
    private int x = 1; 
    public int value()
    {
        return x;
    }
}
但实际,上面的代码是省略一部分状语的。编译器会知道,它表达的其实是如下的意思。
Class B
{
    private int x = 1; 
    public int value()
    {  
        return this.x;
    }
}
变量x其实是实例上的成员。成员函数的使用必须依附于类的实例。当然如果用闭包的设计来理解的话也可以对应的理解成 「实例是其拥有的成员函数的环境」。(不过,你看一些以闭包为特性的语言比如JS里,函数可以捕获很多层作用域之外的变量,因此也较之有着更好的表达力。


大抵就是这样。