JavaScript 作用域与编译原理详解:从词法分析到 LHS/RHS 引用
本文基于 You-Dont-Know-JS 文档,深入解析 JavaScript 的编译过程,包括标记、解析与代码生成三步。重点阐述 LHS(赋值)与 RHS(取值)引用的区别,以及引擎在不同作用域中查找变量时的行为差异。通过具体代码示例,说明非严格模式下全局变量自动创建与 ReferenceError 错误的触发条件,帮助开发者掌握作用域核心机制。
引言
You-Dont-Know-JS是github上一个拥有9000多枚星星的JS教学文档,评价很高,为了避免和其他翻译文一样,容易陷入不宜读的混乱,也试图避免原文中过多数的术语导致我露怯,我只提取提取我理解的一些点,通俗的讲出来。今天先从第一章作用域开始吧:
原文由我的翻译团队 folk,希望有识之士能与我共同完善。
JS 的编译
JavaScript 与传统编程语言一样,在执行前需要经过编译过程。这一过程通常分为三个主要阶段:
- 标记与词法分析(Tokenizing/Lexing):将源代码切分为一个个独立的片段,称为 tokens。例如,语句 var a = 2; 会被切分为以下 tokens:var, a, =, 2, ;。
- 解析(Parsing):将 token 流转换为树状结构,这是一种嵌套元素形式,符合语法结构和规则。这棵树被称为抽象语法树(AST, Abstract Syntax Tree)。
- 以 var a = 2; 为例,生成的树从顶层节点 VariableDeclaration 开始,包含子节点 Identifier(值为 a),以及另一个子节点 AssignmentExpression,该节点又包含子节点 NumericLiteral(值为 2)。
- 代码生成(Code-Generation):此阶段将 AST 转换为可执行代码。由于不同编程语言和平台存在巨大差异,此处忽略细节。简而言之,var a = 2; 会被转换为机器语言,在内存中创建一个变量 a 并将值存储其中。
编译器对赋值的操作
当编译器遇到 var a 时,它会询问当前域是否存在变量 a。如果存在,编译器会忽略该声明;如果不存在,编译器会要求当前域声明一个新变量 a 供自己使用。
随后,编译器为引擎生成可执行代码以处理 a = 2。引擎首先询问当前域是否包含该变量,如果不存在,则执行其他相关操作。
两种引用
存在 LHS(Left-hand Side,左侧)引用和 RHS(Right-hand Side,右侧)引用两种类型。对于编译器而言,LHS 指变量用于赋值操作,而 RHS 指变量用于取值操作。
function foo(a) {
console.log( a ); // 2
}
foo( 2 );在此处,调用 foo() 时,引用(变量)foo 属于 RHS 引用,因为它是用于取值的操作(即获取函数 foo 的值)。在参数 a 被隐含赋值为 2 的过程中,a = 2 这里的引用 a 属于 LHS 引用,因为它用于赋值。而在 console.log(a) 里面的 a 也是 LHS 引用,因为它用于获取 a 的值。
这里说明的是,代码中的变量对于编译器来说分为两种类型:一种用于获取其值,另一种本身用于被赋值。
嵌套域 (Nested Scope)
嵌套域的概念很容易理解,参考以下代码:
function foo(a) {
console.log( a + b );
}
var b = 2;
foo( 2 ); // 4由于 foo 函数域中没有变量 b,当引用 b 被调用时,程序会逐级向上查找,直到找到变量 b,最顶层为全局变量(global scope),如下图所示:

错误
但是各位有没有想过一个问题,被赋值的引用(LHS)如果本域内不存在会产生什么情况?当然,大多数人都经历过 ReferenceError,即引用错误。
当 LHS 引用(赋值引用)在域中找不到时,JS 引擎会直接抛出错误。而 RHS 引用(调用引用)如果在域中找不到时,JS 引擎会向上一级域中查找;如果依然没有,会直接在全局域中为你自动创建一个(非严格模式下,严格模式下也会报 ReferenceError 错误)。
考虑如下代码:
function foo(a) {
console.log( a + b );
}
foo( 2 );这里的变量 b 因为在函数域中未定义,所以会报错。
function foo(a) {
b = a;
}
foo( 2 );这里的变量 b 也未定义,但是由于是赋值操作,系统会为你在外层全局域里自动创建一个。
以上就是 You-Dont-Know-JS 作用域学习文档的详细内容,更多关于 JS 作用域的资料请关注本站其它相关文章!
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
为什么JS中typeof []返回object:数组类型检测误区解析
2026-11-04 19:50:47
