JavaScript只使用词法作用域,完全不支持动态作用域;其作用域链在函数定义时即固化,变量查找严格按代码嵌套结构从内向外逐层进行,同名变量会遮蔽外层变量,找不到则抛ReferenceError。

JavaScript 只使用词法作用域,完全不支持动态作用域。所谓“联系”,其实只是教学上的对比工具——用动态作用域反衬出词法作用域的设计逻辑和行为特征,两者在 JS 中并无共存或混合关系。
词法作用域:看代码写在哪,不是看函数在哪调
函数的作用域链,在它被定义的那一刻就固化了,跟后续怎么调、从哪调、调几层都无关。引擎严格按源码的嵌套结构,从内向外一层层查找变量。
- 比如 bar 在全局定义,即使被 foo 内部调用,它仍沿定义时的链查全局的 x
- inner 在 outer 函数体内定义,就能自然访问 outer 中的变量,哪怕这个 inner 被返回后在别处执行
- 这种静态可推导性,是闭包、模块封装、工厂函数等机制能成立的前提
动态作用域:JS 里不存在,但拿来对比很有用
它只存在于理论对照中,代表另一种变量查找思路:运行时沿着当前调用栈向上找,谁调了我,我就优先用谁作用域里的同名变量。
- Bash 或某些 Lisp 中,bar 被 foo 调用,就会取 foo 里的 x,哪怕 bar 是在全局写的
- 而 JS 做不到这点:let x = "global"; function bar() { console.log(x); } function foo() { let x = "local"; bar(); } foo(); 输出一定是 "global"
- 开发者有时误以为 this 的动态绑定意味着“JS 有动态作用域”,这是混淆——this 是执行上下文的一部分,变量查找仍是纯词法的
遮蔽与查找细节:真实发生的过程
变量查找不是模糊匹配,而是逐级、单向、严格的路径搜索:
立即学习“Java免费学习笔记(深入)”;
- 只查一级标识符:obj.a.b 中,词法作用域只负责找 obj;a 和 b 是属性访问,走的是对象内部机制
- 同名变量会遮蔽外层:块级作用域(let/const)也能遮蔽函数作用域或全局变量
- 找不到就报 ReferenceError,不会静默失败,也不会 fallback 到其他上下文
为什么 JS 必须坚持词法作用域
这不是语法偏好,而是工程实践的必然选择:
- 阅读代码时,无需追踪整个调用链,就能准确判断变量来源
- IDE 补全、ESLint 检查、打包工具摇树优化等,全都依赖静态可分析的作用域关系
- 闭包能持久持有外部变量引用,模块系统能隔离私有状态,底层都建立在“定义即锁定”的确定性之上


















