作用域与作用域链:JS的“找东西”逻辑,闭包到底是个啥?

发布时间:2026/5/20 12:27:46

作用域与作用域链:JS的“找东西”逻辑,闭包到底是个啥? 为什么有的变量在函数里能用在外面却报错为什么循环里的i总是最后一个值今天我们就来聊聊JavaScript的作用域和作用域链顺便揭开闭包的神秘面纱。保证你看完之后再也不用背面试题了。前言想象一下这样的场景你在自己房间里找手机找不到就去客厅找再找不到就去邻居家借手机打电话。如果所有地方都找不到那就只能放弃——手机丢了。JavaScript在查找变量时也是这么个流程。这个“找东西”的规则就是作用域链。而变量能在哪些地方被找到由它的作用域决定。今天我们就来把这件事彻底捋清楚。一、作用域变量的“活动范围”作用域就是变量能够被访问到的范围。JS中有三种主要作用域1. 全局作用域公共场所在函数外面定义的变量或者没加任何关键字直接写的变量严格模式会报错都属于全局作用域。varglobalVar我是全局的;letalsoGlobal我也是全局的;functionsayHello(){console.log(globalVar);// 能访问}全局变量就像公共场所的设施谁都能用但正因为谁都能改所以容易出问题。而且全局变量会一直存在直到页面关闭。2. 函数作用域自己家在函数内部用var声明的变量只能在这个函数内部访问。外面进不去里面可以出去找外面的变量。functionmyHouse(){varsecret我藏起来的零食;console.log(secret);// 能访问}console.log(secret);// 报错secret is not defined函数作用域像自己家外人不能随便进但你可以从家里出去访问全局。3. 块级作用域卧室里的保险柜ES6新增的let和const带来了块级作用域。块就是大括号{}包起来的地方比如if、for、while里面。if(true){letblockVar我只能在块里用;varfunctionVar我可以在整个函数用;// var没有块级作用域}console.log(blockVar);// 报错console.log(functionVar);// 能访问因为var只有函数作用域块级作用域就像卧室里的保险柜只有在这个房间里才能打开。var则像家里的公共区域虽然写在卧室里但实际还是公共的。二、作用域链找变量的路径当你在一个作用域里使用变量时JS引擎会按照这个顺序找当前作用域先看自己家里有没有。外层作用域没有就去上一层找。继续往外一层一层往上直到全局作用域。全局也没有那就报错not defined。这种嵌套的作用域形成的链条就是作用域链。来看个例子varglobal全球通;functionouter(){varouterVar外层的;functioninner(){varinnerVar内层的;console.log(innerVar);// 找到自己家的console.log(outerVar);// 自己家没有去外层找console.log(global);// 自己家没有外层没有再去全局}inner();}outer();这个过程就像你在家找东西先翻自己口袋没有就去客厅找还没有就去小区便利店再没有就只能放弃了。三、闭包虽然离开了但我还记得闭包是JS里一个常考常新、常学常忘的概念。简单来说闭包就是函数记住了它定义时的作用域即使这个函数在其他地方执行也能访问那个作用域里的变量。举个例子functioncreateCounter(){letcount0;// count 被闭包记住了returnfunction(){count;console.log(count);};}constcountercreateCounter();counter();// 1counter();// 2counter();// 3这里createCounter执行后返回了一个函数按说count应该被销毁了但返回的函数依然能访问count——这就是闭包的力量。闭包的生活比喻想象你从小长大的家后来搬走了但你还记得家里的WiFi密码。每次你路过楼下还能连上那个WiFi。这个“记住密码”的能力就是闭包。闭包的用途数据私有化比如上面的计数器外部无法直接修改count函数工厂生成特定功能的函数回调函数中保持状态比如事件监听闭包的坑闭包虽然好用但也要注意内存问题。因为被记住的变量不会释放如果闭包一直存在这些变量就会一直占用内存。比如上面例子只要counter这个函数还在count就不会被垃圾回收。四、经典面试题循环中的var这是JS初学者最容易踩的坑之一for(vari0;i5;i){setTimeout(function(){console.log(i);},100);}你期望输出0,1,2,3,4但实际输出5,5,5,5,5。为什么因为var没有块级作用域循环里的i其实是全局或函数级的同一个变量。循环结束后i变成了5然后setTimeout的回调执行时访问的都是同一个i所以全是5。解决方式用letlet有块级作用域每次循环都会创建一个新的变量。for(leti0;i5;i){setTimeout(function(){console.log(i);// 0,1,2,3,4},100);}用闭包老办法for(vari0;i5;i){(function(j){setTimeout(function(){console.log(j);},100);})(i);}用立即执行函数创建新的作用域把每次的i传进去保存下来。五、词法作用域写在哪就在哪找JS采用的是词法作用域也叫静态作用域也就是说变量的查找范围在代码编写时就决定了而不是在运行时。varvalue1;functionfoo(){console.log(value);}functionbar(){varvalue2;foo();// 输出什么}bar();// 输出1这里foo定义在全局所以它访问的value是全局的1而不是bar里的2。因为作用域由函数定义的位置决定而不是调用位置。这个特性是闭包能工作的基础。六、执行上下文运行时的小剧场作用域是静态的规则而执行上下文是运行时动态的环境。每当函数执行都会创建自己的执行上下文里面包含了变量、参数、以及对外部作用域的引用。执行上下文有点像每次进家门时拿的钥匙串上面有自己家的钥匙还有父母家的钥匙通过作用域链。七、总结今天你学到了什么作用域就是变量的可见范围全局公共场所、函数自己家、块级卧室保险柜。作用域链就是找变量的路径当前 → 外层 → 全局找不到就报错。闭包是函数记住了它出生时的环境即使离开了也能访问那些变量。用途广泛但要注意内存。词法作用域意味着变量的查找在写代码时决定和运行位置无关。循环中用var容易踩坑用let或闭包解决。现在你再看到作用域相关的问题应该能像老司机一样游刃有余了。明天我们将继续深入聊聊JavaScript里最让人迷惑的概念之一闭包的应用场景和内存管理看看闭包在实际项目中到底怎么用怎么避免内存泄漏。如果你觉得今天的文章对你有帮助点个赞让更多人看到也欢迎在评论区聊聊你遇到过的作用域坑。我们明天见

相关新闻