词法作用域指函数的作用域链由定义位置静态确定,执行时动态组装:ao拼接[[scope]];变量查找从左到右逐层匹配,同名变量内层遮蔽外层;闭包通过保留外层ao引用实现对外部变量的持续访问。

函数执行上下文中的作用域链,是在函数被调用时动态构建的,它不取决于函数在哪被调用,而取决于函数在哪被定义——这就是词法作用域的核心。
作用域链的形成过程
作用域链由两部分拼接而成:当前函数的活动对象(AO) + 函数创建时保存的[[Scope]]属性。
- 函数定义时,引擎会把其外层词法环境(即父级作用域的变量对象)存入内部属性[[Scope]]中。比如在全局中定义的函数,[[Scope]]只含全局对象;在另一个函数内定义的函数,[[Scope]]就包含外层函数的AO和全局对象。
- 函数真正执行时,先创建自己的AO(含参数、let/const声明、函数声明等),再将AO插入到[[Scope]]链的最前端,构成完整的作用域链:Scope = [AO].concat([[Scope]])。
- 这个链是静态确定、动态组装的:静态指结构由代码书写位置决定;动态指AO只在调用时才生成并加入链首。
变量查找的具体规则
查找变量时,引擎严格按作用域链从左到右(即从内到外)逐层匹配,找到第一个同名绑定即停止,不会继续向上搜索。
- 先查当前AO(比如函数内用let声明的变量、形参、内部函数声明)。
- 没找到,就查[[Scope]]里的第一个外层环境(如外层函数的AO)。
- 继续向下查,直到全局对象(GO);若全程无匹配,抛出ReferenceError。
- 同名变量存在“遮蔽”效应:内层声明会覆盖外层同名变量,哪怕外层变量真实存在且未被删除。
闭包与作用域链的关系
闭包之所以能持续访问外层变量,正是因为内部函数的作用域链中保留了对外层词法环境的引用——即使外层函数执行完毕、AO本该销毁,只要内部函数还存在,对应的外层AO就不会被垃圾回收。
- 例如counter函数返回一个内部函数,后者的作用域链始终包含counter执行时的AO(含count变量)。
- 这种引用关系是隐式维持的,开发者无法直接访问或修改作用域链,但可通过调试器(如Chrome DevTools的Scope面板)观察它。
- 块级作用域(let/const)也会参与作用域链,ES6后每个块(如if、for)都可能生成独立的词法环境节点。











