词法作用域在编译阶段就已确定,由函数或块在源码中的书写位置决定,而非运行时调用关系;引擎通过词法分析构建静态作用域链,变量查找严格沿此链向上进行。

JS 中词法作用域在编译阶段就已确定,不是靠运行时调用关系,而是靠代码写出来的结构位置。简单说:你把函数写在哪一层嵌套里,它将来就能访问哪一层的变量,这个关系在代码刚被读进引擎、还没执行前,就已经锁死了。
编译时扫描代码结构,构建作用域层级
JavaScript 引擎在执行前会先进行编译(实际是即时编译,耗时几微秒),其中关键一步是词法分析(Lexing)和语法解析(Parsing):
- 把源码拆成词法单元(比如
function、var、变量名、花括号等) - 再把这些单元组织成抽象语法树(AST),清晰反映函数嵌套、块结构、声明位置
- 编译器根据 AST 中的嵌套关系,直接推断出每个函数的“父级作用域是谁”
例如:function inner() { console.log(a); } 写在 function outer() 内部,AST 就会标记 inner 的外层作用域是 outer 的词法环境——这个绑定不依赖 inner 是谁调用的,只看它“出生地”。
词法环境作为静态容器,在编译期生成
编译阶段会为每个函数体预先创建一个词法环境(Lexical Environment)对象,它包含:
将一个或多个视频上传至YouTube。当用户想“上传到YouTube”“发YouTube”“批量上传”“upload to YouTube”“post videos to YouTube”或发布到YouTube时使用此技能。
- 自己的变量/函数声明(如
let b、function fn) - 一个指向外部词法环境的引用(
outer指向全局,inner指向outer)
这个引用链不是运行时动态拼的,而是编译器根据代码缩进、大括号嵌套、函数声明位置,直接写死的。所以哪怕 inner 后来被返回到全局调用,它的 outer 引用依然指向当初定义时的 outer 环境。
变量查找走的是这条静态链,不是调用栈
执行时遇到变量引用(比如 console.log(a)),引擎按以下固定路径查找:
- 先查当前函数的词法环境记录
- 没找到 → 查它词法环境的
outer引用所指向的环境 - 继续向上,直到全局;找不到就报
ReferenceError
注意:这个“向上”是沿着编译时确定的词法环境链,不是跟着当前调用栈往上翻。即使 inner 是从 setTimeout 或事件回调中触发的,它仍沿原定路径找 a,不会去查 setTimeout 的作用域。
let/const 和 var 的区别不影响词法性,只影响绑定时机
ES6 的 let 和 const 引入了块级作用域,但仍是词法作用域:
- 它们的作用域边界由大括号
{}在代码中的位置决定(比如if块、for块) - 编译阶段同样会为这些块建立独立的词法环境,并设置好
outer链 - 唯一不同是:var 声明会被提升并初始化为
undefined,而 let/const 在声明前处于“暂时性死区”(TDZ),但这属于绑定规则,不改变作用域本身的静态性










