本文深入解析JavaScript的词法作用域与作用域链机制。详细阐述词法作用域的四大核心规则,包括函数对外部数据的访问权限及变量遮蔽效应。通过绘制作用域链图解,演示变量在多级链式结构中的查找流程与提升策略,帮助开发者准确理解变量作用范围及闭包基础。
本文通过具体实例,详细解析JavaScript中的词法作用域及其作用链机制,旨在为读者提供清晰的参考与理解。
一、作用域
所谓“域”即范围,也就是作用域,它界定了标识符在何处可用、何处不可用。若需深入理解,建议参阅《你不知道的JavaScript(上卷)》第一章,该书从编译原理视角阐释了作用域的本质。简而言之,作用域是一套精心设计的规则体系,用于存储变量并在后续操作中便捷地检索这些变量。
1.1 块级作用域
在C、Java、C#等编程语言中,执行如下伪代码会引发报错:
{
var num = 123;
{
console.log( num ); // num => 123
}
}
console.log( num ); //报错
1.2 JS的词法作用域
词法作用域(亦称静态作用域)是指作用范围在代码编写阶段即已确定,无需执行即可知晓。只要代码编写完成,其作用范围便已固定,这便是词法作用域的核心定义。
JavaScript中词法作用域遵循以下规则:
- 函数内部可以访问函数外部的数据。
- 在整个代码结构中,唯有函数能够限定作用域的范围。
- 作用域的判定首先依据提升规则进行分析。
- 若当前作用域内已存在同名标识,则忽略外部同名标识。
示例1:
var num = 123;
function foo(){
console.log( num );
}
foo(); //123
示例2:
if( false ){
var num = 123;
}
console.log( num ); //undefined
示例3:
var num = 123;
function foo () {
var num = 456;
function func(){
console.log( num );
}
func();
}
foo(); //456
二、作用域链
只有函数能够构建作用域结构。因此,任何代码至少包含一个作用域,即全局作用域。
只要代码中存在函数,该函数便构成一个独立的作用域。若函数内部还嵌套有其他函数,则在该作用域内又可生成新的作用域。将所有此类作用域按层级排列,便形成从函数内部指向外部的链式结构,即作用域链。
例如:
function f1() {
function f2() {
}
}
var num = 456;
function f3() {
function f4() {
}
}
作用域链的结构与DOM树结构具有相似性。
2.1 绘制作用域链
绘制步骤如下:
- 首先将整个全局环境视为一条顶级链,记为0级链。
- 接着查看全局作用域中声明了哪些成员,并以方框形式将其绘制在0级链上。
- 随后寻找函数,由于只有函数能限制作用域,因此从函数内部引出新的链,标记为1级链。
- 最后,在每一个1级链内部重复上述行为,继续展开更深层的链。

2.2 变量的访问(搜索)规则
- 首先确定变量位于哪条链上,若该链上存在变量的定义与赋值,则直接使用。
- 若当前链未找到,则向上一级链(即 n - 1 级链)查找;若找到,则直接使用并停止查找。
- 若仍未找到,则继续向上层查找,直至全局链(0级)。若全局链中也不存在,则报错“is not defined”。
- 特别注意:同级的链之间不可混合查找。
请绘制以下程序的作用域链:
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1(); //123

函数f1与变量num=456位于0级链,而f1内部可展开1级链,1级链上包含num=123和函数f2。当程序调用f1()时,进入左侧1级链;由于f1内部调用了f2函数,f2中的console.log(num)可视作位于2级链。此时,程序会沿该链向上查找:首先2级链中无num,向上到达1级链,恰好1级链上有num=123,因此直接使用该值,最终打印结果为123。
2.3 如何分析代码
- 分析代码时,务必从代码运行的角度进行审视。若代码对变量进行了赋值,必须在图中明确标记。
- 若代码逻辑较为复杂,可在图中标注代码的具体内容,有时还需结合原型图与作用域图进行综合解析。
分析以下代码:
var num = 123;
function f1() {
console.log( num );
}
function f2(){
var num = 456;
f1();
}
f2(); //123
作用域链图:

首先将num=123、函数f1和函数f2绘制在0级链上。f1中仅包含一句console语句,由此引出一条1级链;f2下方同样画出一条1级链,该链上包含num=456及函数调用语句f1();
调用f2()后,进入f2函数的作用域链;而在f2内部又调用了f1函数,程序随之进入f1的作用域链。因此,console.log(num)会在此链上查找是否存在num,若未找到,则继续向上一级链查找,恰好0级链上存在num=123,故f1函数中的console.log(num)结果为123。
感兴趣的朋友可使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码的运行效果。
更多关于JavaScript相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。