JS闭包:变量的"守护者"与"储物间"

前言:JS世界的"潜规则"

JavaScript这门语言,表面看起来简单粗暴,实则暗藏玄机。今天我们要聊的"闭包",就是JS中最让人头疼又欲罢不能的特性之一。它像一个神秘的守护者,既保护着变量不被外界干扰,又像一个舍不得扔东西的储物间,可能让你的内存悄悄"膨胀"。

声明提升:JS引擎的"预习"习惯

先问个灵魂问题:为什么下面这段代码不会报错?

javascript 复制代码
console.log(a); // 输出undefined,而不是报错
var a = 1;

这就要怪JS引擎的"预习"习惯了------声明提升。它会在执行代码前,先把变量和函数的声明部分"偷偷"提到作用域的顶部。就像你考试前先看一遍题目,心里有数了再答题。

所以上面的代码,在JS引擎眼里其实是这样的:

javascript 复制代码
var a; // 声明被提升
console.log(a); // 输出undefined
a = 1; // 赋值还在原地

不过要注意,let和const声明的变量可没有这种"特权",它们不会被提升哦!

调用栈:函数的"排队系统"

想象一下,你去银行办理业务,柜员会给每个人发一个号,按顺序叫号办理。JS里的调用栈就像是这个"排队系统",它追踪着函数的调用顺序,管理着代码的执行关系。

但这个队列有个特点------后进先出。最后进来的函数先执行完,就像你挤地铁时,最后挤上去的人往往先下车(如果他能挤得下去的话)。

不过调用栈不能设计得太大,否则JS引擎在查找上下文时会花费大量时间,就像排队的人太多,银行大厅会变得混乱不堪。

块级作用域:变量的"独立房间"

在ES6之前,JS只有全局作用域和函数作用域,这就导致了很多奇怪的问题。比如:

javascript 复制代码
for (var i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}
// 猜猜输出什么?是5个5,而不是0,1,2,3,4

这是因为var声明的变量没有块级作用域,i在循环结束后变成了5。

ES6引入的let和const解决了这个问题,它们和{}结合,创建了真正的块级作用域。就像给变量分配了独立的"房间",互不干扰:

javascript 复制代码
for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}
// 这次输出的是0,1,2,3,4

作用域链:变量的"查找地图"

当JS引擎需要查找一个变量时,它不会像无头苍蝇一样乱撞,而是有自己的"查找地图"------作用域链

它会先从当前作用域查找,如果没找到,就向上一级作用域查找,直到找到为止,或者一直找到全局作用域。就像你在家里找不到钥匙,会去客厅找,客厅找不到再去小区物业问问。

这个"查找地图"的下一站是哪里,是由一个叫outer指针的"导航员"决定的。它指向当前作用域的外部作用域,引导着JS引擎一步步向上查找。

闭包:变量的"守护者"与"储物间"

终于轮到主角闭包登场了!闭包到底是什么?简单来说,它是一种特殊的作用域现象。

根据作用域链的规则,内部函数一定有权利访问外部函数的变量。但问题来了:一个函数执行完毕后,它的执行上下文不是应该被销毁吗?

当函数A内部声明了一个函数B,而函数B被拿到A的外部执行时,JS引擎为了保证上述规则正常执行,会做出一个特殊的决定------A函数在执行完毕后,会将B需要访问的变量保存在一个集合中,并留在内存中。这个集合,就是我们所说的闭包。

举个例子:

javascript 复制代码
function outer() {
  let secret = '我是秘密';
  return function inner() {
    console.log(secret); // 内部函数访问外部函数的变量
  };
}

const getSecret = outer(); // outer执行完毕,但secret并没有被销毁
getSecret(); // 输出:我是秘密

这里的inner函数就是一个闭包,它"守护"着secret变量,不让它被垃圾回收机制回收。

闭包的"双面性"

闭包既有用,也有潜在的问题:

好处

  • 可以让函数访问其定义时的词法作用域,即使函数在其他地方执行
  • 可以创建私有变量,避免全局污染
  • 可以实现模块化编程

坏处

  • 闭包会导致变量无法被及时回收,可能造成内存泄露
  • 如果闭包使用不当,可能会导致内存占用过高

所以使用闭包时,我们要像使用一个锋利的工具一样------发挥它的作用,但也要小心不要伤到自己。

如何避免闭包导致的内存泄露?

  • 当不再需要闭包时,手动将引用设置为null
  • 避免在循环中创建过多闭包
  • 使用弱引用数据结构(如WeakMap)来存储闭包中的数据

总结:闭包的本质

闭包的本质,是JS引擎为了保证作用域链查找规则和函数执行上下文销毁规则不冲突而做出的妥协。它像一个忠实的守护者,保护着内部函数需要的变量;但也像一个舍不得扔东西的储物间,可能让你的内存悄悄膨胀。

理解闭包,不仅能帮你写出更优雅的代码,更能让你深入理解JS的执行机制。下次再遇到闭包相关的问题,希望你能像个老司机一样,轻松应对!

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax