JavaScript高级①|作用域与闭包,从原理搞懂代码的可见范围
author: 专注前端开发分享JavaScript干货title: JavaScript高级①作用域与闭包从原理搞懂代码的可见范围update: 2026-04-28tags: JavaScript,作用域,闭包,词法作用域,执行上下文,作用域链,前端进阶作者专注前端开发分享JavaScript干货更新时间2026年4月适合人群有JS基础想深入理解执行机制的开发者前言作用域与闭包为什么重要作用域决定了一个变量在哪里看得见。闭包是JS最强大的特性之一也是面试高频考点。很多疑难杂症循环里的异步、变量泄漏、内存问题——本质上都是作用域和闭包的问题。一、作用域的类型1.1 全局作用域// 顶层声明的变量属于全局作用域varglobalVar全局变量;functiontest(){// 函数内部可以访问全局变量console.log(globalVar);// ✅}test();// 但在函数内部声明的变量外面访问不到functiontest2(){varinsideVar函数内部变量;}// console.log(insideVar); // ❌ ReferenceError1.2 函数作用域var// var 在函数内声明只在函数内有效functionouter(){varx10;if(true){vary20;// var 不受块级作用域影响console.log(x);// ✅ x在这里可见}console.log(y);// ✅ y在这里也可见var的特性}console.log(x);// ❌ x在外面访问不到1.3 块级作用域let / const// let 和 const 有块级作用域functionouter2(){letx10;if(true){lety20;constz30;console.log(x,y,z);// ✅ 全部可见}console.log(x);// ✅// console.log(y); // ❌ y只在这个块里有效// console.log(z); // ❌ z只在这个块里有效}二、词法作用域静态作用域JavaScript采用的是词法作用域静态作用域即函数的作用域在定义时就决定了而不是运行时。varvalue1;functionfoo(){console.log(value);// 引用外层的value}functionbar(){varvalue2;foo();// 打印的是1不是2}bar();// 输出1分析foo定义时外层能看到的value是全局的1bar调用时把局部value改成了2但foo的词法环境不变。三、作用域链当访问一个变量时JavaScript会从内到外逐层查找直到找到为止找不到就报错。vara1;functionlevel1(){varb2;functionlevel2(){varc3;functionlevel3(){vard4;console.log(a,b,c,d);// a → 全局找到// b → level1找到// c → level2找到// d → level3找到}level3();}level2();}level1();// 输出1 2 3 4四、闭包Closure4.1 闭包的本质闭包 函数 该函数能访问的外部变量。functionouter(){varcount0;// 外部变量functioninner(){count;// 内部函数引用了外部变量returncount;}returninner;// 把inner函数返回}constcounterouter();// outer已经执行完毕console.log(counter());// 1count被inner记住了console.log(counter());// 2console.log(counter());// 3// outer虽然执行完了但它的count变量依然被inner引用着没有被回收4.2 闭包的经典场景计数器functioncreateCounter(){letcount0;return{increment(){count;},decrement(){count--;},getCount(){returncount;}};}constcountercreateCounter();counter.increment();counter.increment();counter.decrement();console.log(counter.getCount());// 1防抖函数闭包定时器functiondebounce(fn,delay){lettimernull;returnfunction(...args){clearTimeout(timer);timersetTimeout((){fn.apply(this,args);},delay);};}consthandleInputdebounce(function(value){console.log(发送搜索请求,value);},500);// 每次输入都清除上一个定时器设置新的500ms定时器input.addEventListener(input,(e)handleInput(e.target.value));4.3 循环中的闭包问题高频面试题// ❌ 错误写法所有点击都输出6for(vari0;i6;i){setTimeout(()console.log(i),100);}// 输出6 6 6 6 6 6循环结束后i变成6// ✅ 解决方案1用letlet有块级作用域每次循环i都是新变量for(leti0;i6;i){setTimeout(()console.log(i),100);}// 输出0 1 2 3 4 5// ✅ 解决方案2立即执行函数IIFE捕获ifor(vari0;i6;i){(function(j){setTimeout(()console.log(j),100);})(i);}// 输出0 1 2 3 4 5五、内存泄漏与闭包闭包会阻止外部变量被垃圾回收要注意// ❌ 可能造成内存泄漏大型数据被闭包持有functionheavyProcess(){constbigDatanewArray(1000000).fill(x);// 占用大量内存returnfunction(){returnbigData[0];// bigData永远不会被释放};}// ✅ 正确做法用完主动释放functionheavyProcessFixed(){constbigDatanewArray(1000000).fill(x);constgetFirstfunction(){returnbigData[0];};// 用完后清空引用bigDatanull;returngetFirst;}六、知识卡概念说明全局作用域整个程序任何地方都可见函数作用域var仅在函数内部有效块级作用域let/const仅在{}内部有效词法作用域函数定义位置决定作用域作用域链从内到外逐层查找变量闭包函数 可访问的外部变量循环闭包问题用let或IIFE解决七、课后作业分析下面代码的输出结果并解释原因for(vari0;i3;i){setTimeout(function(){console.log(i);},100);}用闭包实现一个记忆函数相同参数直接返回缓存结果写一个函数使其返回一个函数数组每个函数返回自己的索引0, 1, 2…有问题欢迎评论区留言大家一起讨论标签JavaScript | 作用域 | 闭包 | 词法作用域 | 执行上下文 | 作用域链 | 前端进阶