JavaScript词法作用域在编译阶段即完全确定,由函数定义位置静态决定;引擎通过词法分析切分token、构建AST并绑定[[Environment]],变量查找严格遵循“自身→外层→全局”路径,遮蔽关系与执行时机无关。

JavaScript 词法作用域在代码编译阶段就已完全确定,不依赖运行时行为。它的核心是“写在哪,就属于哪”,整个结构由词法分析器在读取源码的最初几微秒内静态建立。
词法分析阶段就完成作用域划分
引擎一拿到源码,立刻进入词法分析(Lexing):把代码切分成 token,比如 function、let、{、变量名等。这个过程不执行逻辑,只识别语法结构。
- 遇到
function outer() { ... },引擎标记:此处开启一个新作用域,父级为当前所在作用域(通常是全局) - 若内部有
function inner() { ... },引擎当场确认:inner 的词法父级就是 outer 的作用域 - 所有
let/const/var声明,按其出现的嵌套层级,直接登记进对应作用域的记录表中
函数定义时绑定 [[Environment]]
每个函数在 AST 构建完成后,引擎会为其分配一个内部属性 [[Environment]],它指向函数诞生那一刻所处的词法环境。这个引用是静态写死的,不是运行时推导的。
- 例如
function makeGreeting(name) { let msg = 'Hello'; return () => console.log(msg); },箭头函数一被定义,[[Environment]] 就绑定了 makeGreeting 的词法环境 - 哪怕 makeGreeting 执行完毕、上下文销毁,返回的函数仍能访问
msg和name - 这个绑定关系不会因函数被赋值给全局变量、传入
setTimeout或作为回调传递而改变
变量遮蔽与查找路径在编译期固化
同名变量谁生效、变量从哪一层查起,完全由嵌套位置和声明顺序决定,编译阶段就锁定。
-
let x = 1; function foo() { let x = 2; console.log(x); }中,foo 内部一定输出 2 —— 因为内层x在词法上遮蔽了外层,该关系与调用时机无关 - 变量查找严格走 “自身 → 外层 → 全局” 路径,本质就是顺着编译期建好的 [[Environment]] 链向上跳
- 这种静态可预测性,让 ESLint、IDE 补全、TypeScript 类型检查能在编辑时就准确定位变量来源
和动态作用域的本质区别
动态作用域看“谁调用了我”,词法作用域只认“我写在谁里面”。JS 不会因为 bar() 是在 foo() 里被调用,就让 bar 去访问 foo 的局部变量;它只查自己定义时所在的那条链。
- 这种“无视调用现场”的稳定性,是闭包成立的前提:函数携带的是定义时刻的完整词法环境地图,而非运行时快照
- 地图一旦生成,运行期间不更新、不重算、不迁移
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











