词法作用域的核心是变量查找依赖函数定义位置而非调用位置;其作用域链在定义时固化,不受调用栈影响,闭包是其自然结果,且需避免eval和with干扰。

词法作用域的核心判断标准,就是看一个函数访问变量时,到底依赖它“写在哪”还是“在哪被调用”。只要变量查找路径完全由源码中的嵌套位置决定,不受调用位置影响,那就是词法作用域在起作用。
看函数定义的位置,而不是调用的位置
函数内部能读到哪些变量,只取决于它在代码里被声明时所处的嵌套层级。比如一个函数直接写在全局下,那它永远只能向上查全局;如果写在另一个函数内部,它就能访问那个外层函数的变量——哪怕这个内层函数后来被返回、传参、赋值给全局变量,它的访问权限也不会变。
- 定义在全局的函数:作用域链是 [自身 → 全局]
- 定义在 outer 函数内的 inner 函数:作用域链是 [inner → outer → 全局]
- 即使 inner 被 return 出去,在别处执行,链依然不变
变量查找不随调用栈变化
当函数执行中遇到未声明的变量,JS 引擎不会顺着调用顺序(比如谁调了我)去找,而是严格沿着它定义时的外层结构一层层往上找。这是最直接的识别方式。
- 典型反例:bar 定义在全局,里面访问 x;foo 定义在局部并声明了 let x = "local";然后 foo 调用 bar —— bar 仍然输出全局的 x,不是 foo 的 x
- 这说明 bar 的变量解析没受调用者 foo 影响,只认自己出生地(定义位置)
闭包是词法作用域的自然结果
如果一个函数能持续访问它定义时所在环境中的变量(哪怕外层函数已经执行结束),那就证明它的作用域链在定义那一刻就被固化了。
- 比如 createCounter 返回一个函数,该函数每次调用都还能读写定义时捕获的 count 变量
- 这种“记住出生环境”的能力,正是词法作用域静态绑定的体现
- 动态作用域做不到这点,因为它没有“记住”,只看当下调用上下文
避免 eval 和 with 的干扰
这两个特性会临时改变变量查找规则,让原本静态的作用域链在运行时被修改。一旦用了它们,就无法干净地观察词法作用域行为。
- eval 内部的变量可能动态创建或覆盖外层同名变量
- with 会把传入对象临时加进作用域链顶端,破坏静态结构
- 所以识别词法作用域时,应确保代码不含这两者











