闭包就是一个不会瘪掉的气球。或者说是一种特殊气球运用的一种手段,使得它自己不会瘪下去。

JavaScript 的世界,就是一个气球世界。在一个运行着的 JavaScript 程序里,不断有气球被吹起来,又瘪下去。(就像三体人,会脱水,又能吸水复活)

你大概知道,JavaScript 里函数是一等公民。一般来说,当函数开始执行,你就会看到一个气球被吹起来了。当函数执行完毕,气球就瘪下去了。

javascript function bubble() { const hello = 气球这时被吹起来了;

return hello;

// 这时气球即将瘪下去 }

bubble() bubble()

这个气球,就是 JavaScript 的作用域。气球里,基本上装的就是变量。如果一个函数里变量很多,每个变量存的数据很多,当函数执行时,这个气球就会被吹得很大。

但无论这个气球被吹得多大,当函数执行完毕,其中的变量会被垃圾回收程序回收掉,于是这个气球就瘪掉了。

你应该知道,垃圾回收时,是看它被引用的计数是多少,一旦引用计数是 0,就会被回收掉。一般气球里的变量,也就是函数内定义的变量,只在函数里被引用,所以当函数执行完毕,引用计数就为 0 了。所有的变量都被回收,气球也就破灭了。(如果你看过《寻梦环游记》这部电影,可以好好回味一下它的主题曲《Remember me》:阴间的亡灵都有一个引用记数,即被多少阳间的人记着。一旦阳间所有人都忘记了那个亡灵,那么它就真正地永远消逝了。)

前面说的都是一般的函数,或者说一般的气球。还有另一种函数:高阶函数,即返回函数的函数。这种函数,也就是气球,一旦吹起来,就不会瘪下去(除非程序中止):

javascript function outerBubble() { const hello = 气球被吹起来;

return function innerBuble() { const world = 小气球被吹起来

return hello;
// world 即将死去,小气球会瘪下去

} // 这时小气球瘪了,但是外面的大气球却不会瘪:hello 还活着 }

const bubble = outerBubble(); bubble(); bubble();

这里注意:函数最终都是要返回一个值的。即使没有 return 语句,也是返回了一个 undefined。高阶函数很特殊,它返回了一个函数:也就是它吐出了另一个气球。

一般的气球(一阶函数)吐出的是一个普通的值,外界拿到了这个值,就把气球放气了,因为暂时不需要了,目的已经达到。但是要注意,气球瘪下去,只是消灭了气球内部的变量,而气球本身还在,可以随时被再吹起来。只是再吹起来时,里面的变量都是重新生成的,之前死去的变量,是真正的死去了,就像人生轮回一样,那些变量既不知道它的前世,也无法控制它的来世。只是冥冥之中,它们是“同一个”变量。

高阶函数就狡猾了,它不直接吐出这个要命的值,而是把这个值,用另一个气球包起来后,再吐出去。这时外界收到另一个气球(尽管是瘪着的状态),就不敢把原来的气球放气压瘪,也就是原来的气球里的变量(只要被包在了新的气球里),就不会死亡。外界(程序)要拿到那个最终的值,需要执行那个被返回的函数,也就是需要吹起那个被吐出来的气球。尽管拿到那个值后,会把被吐出来的气球放气、压瘪,但不能保证后面它还会被吹起来,所以原来的气球里的那个被直接或者间接包起来的变量,其引用计数就一直大于 0,垃圾回收程序看到它都会绕着走。

就这样,通过吐出另外一个气泡的小把戏,这个高阶函数就能一直处于充气的状态。尽管,它可能不会把所有的变量都包在另外的气球里,所以它执行完可能体积会变小一些,但不会被完全压瘪:有那些它想保护起来的变量(们)撑着呢。

这种耍小把戏的高阶函数,就是闭包函数。这种小把戏的名字,就叫闭包:因为它吐出(返回)前,将一些东西包了起来。