想象一下你在 JavaScript 世界里写代码,有一条命令说:“把后续 10 年每个月都冻结,哪怕最终 10 年这年通货膨胀快得吓人,钱也得按目前的价格结算。”结局你发现这 10 年每个月都收到了钱,没用过一次冻结功能。

这就是闭包,它让代码能“偷看”邻居的隐私,要么更准地说,让一个函数像幽灵一样,在内存的某个角落里赖着不走。 别急着找教科书式的定义,咱们直接看代码。当你写一个函数 `fn`,然后把它赋值给另一个变量 `wrapper`,这就怪了。`fn` 自己本来是个可重用的函数,但它被 `wrapper` 给“绑架”了。

这时候你就拥有了一个闭包闭包的核心在于,函数内紧挨着外部声明的功能域。

这就好比你在海边钓鱼,你需求一个鱼竿,但鱼竿是别人递给你的,你手里拿着鱼竿的与此同时,却还得用别人的网兜捕鱼。 闭包之故此如此神奇,是出于它们拥有自己的“内存空间”。当你创建一个闭包变量时,你创建了两个层层的区域。一层是函数内部,一层是外部包裹它的变量。最妙的是,这层外部区域一辈子不会被垃圾收集器回收,哪怕那个外层变量已经死了。 举个例子,咱们写个计数器。假设初始值是 0,每加 1 就用这个计数器。`wrapper` 就是一个闭包,它把自己锁死在 `count` 函数上面。

这时候要是 `count` 里的 `count` 变成 5,`wrapper` 还拿着那个初始的 0。

要是你后来把 `wrapper` 扔进回收站,哪怕 `count` 已经改到 10 了,`wrapper` 里那个“初始值 0"一辈子悬空在那里,只是个没用的占位符。

这就是闭包的威力,它把状态锁定了。 实际上内置函数也是闭包。`Array.prototype.map`、`forEach` 这些,都是闭包。你只需求在调用它们时给它传个闭包参数,哪怕这个参数后来被销毁了,它的本事也不会消亡。 再深入点看,闭包还能在函数之间传递值,让那些本该一次性用完的数据,在需求的时候还能用一次。

比如你写个函数 `createDate`,它里面有个日期,然后回一个对象 `Date.new`。当你调用 `Date.new()` 时,它把 `Date.new` 作为参数传进去。

后来你把 `Date.new` 扔掉,要么调用完它,它都是闭包的一局部,里面的日期值依然有效。 当你在函数里直接引用一个外部变量,而这个变量又引用了另一个外部变量,这就形成了嵌套闭包。就像是你左手拿着一个文件,右手拿着另一份文件,文件里写着你右手文件的内容。你打开左手文件,看到的内容实际上包含了右手文件当时写下的所有信息,哪怕你后来把右手文件删了,左手文件里的内容也不会变。 JS 引擎处理闭包时,会维护一个栈要么一些复杂的表格,来追踪变量的功能域链。当你在函数里访问一个非局部变量时,引擎会沿着功能域链往上爬,直到找到这个变量。

要是链断了,就会报错。

要是找到了,那这个变量的值就是由闭包里的函数供给的。 这种机制让闭包成为处理回调函数、工厂函数和执行上下文的关键。但在实际开发中,大家也会嘟囔它难搞。

比如你在创建 `Date` 对象时,要是不小心把 `Date` 的构造函数参数传错了,要么传了一个不是对象的东西,闭包就会报错。

这就是功能域链断掉了。 有时候闭包的难题在于泄露。你创建了一个闭包对象,把它存起来,但后来忘记释放了内存。别看 JS 有垃圾回收机制,但有时候性能会受影响,要么你发现那个对象里的值在某个地方被无限地重复使用,就连被篡改。 还有一种情况,是闭包嵌套得忒深,害得变量功能域混乱。

比如你在递归函数里定义了变量,然后试图通过闭包去访问它。

要是递归层数忒多,变量可能根本找不到,就害得运行时毛病。 要是在开发中遇到闭包,一般的做法是尽量避免直接引用外部变量,要么在闭包内部使用 `let` 和 `const` 来对声明功能域。

要是务必访问外部变量,确保变量的功能域链没有断裂。对于工厂函数,记得把参数封装成对象,要么把参数作为参数传给闭包。 总而言之,闭包不是魔法,它只是对内存管理的一种特殊理解。它准函数“记住”它“看到”了哪些变量。对于初学者来说,这东西让人头大,但对于娴熟的开发者来说,闭包是构建复杂逻辑、模块化代码的基石。它让代码在内存中灵活得像个自由体,却又带着严格的规则,不让你随意破坏别人的隐私地带。