静态作用域指变量作用域由定义时代码层级决定,而非调用时位置;闭包捕获定义时enclosing层变量,按legb顺序逐层查找,不跳过中间层,且作用域链在函数创建时即固化。

静态作用域在闭包嵌套中不是靠“运行时调用位置”决定变量归属,而是由函数定义时的代码层级结构锁定查找路径。变量覆盖和查找顺序都严格遵循这一静态绑定,不因调用方式改变。
变量查找永远按LEGB顺序逐层向外,不跳过中间层
Python、JavaScript、PHP等主流语言均采用LEGB(Local → Enclosing → Global → Built-in)查找链。关键在于:Enclosing指**直接外层函数的作用域**,不是任意外层,也不是调用栈上的上层函数。
- 即使有三层甚至四层函数嵌套,inner函数只认它定义时紧挨着的那一个外层函数(enclosing),不会跨过中间层去查更外层的同名变量
- 如果enclosing层没有该变量,才继续向上查global;不会因为global里有就跳过enclosing层的声明
- 例如:def a(): x = 1; def b(): x = 2; def c(): print(x); return c; return b —— 这里c定义在b内部,所以它的enclosing是b的作用域,输出2;哪怕a里也定义了x=1,c也不会访问它
变量覆盖只发生在同一作用域内,跨层不构成覆盖
所谓“覆盖”,是指同一作用域中后声明的同名变量替代前声明的。不同嵌套层级的同名变量互不干扰,各自独立存在。
- 局部变量永远不会“覆盖”全局变量,只是在LEGB查找中优先被找到,掩盖了外层同名变量的存在
- 闭包捕获的是定义时enclosing层的变量引用,即使外层函数多次执行生成多个闭包实例,每个闭包持有的是各自独立的enclosing变量副本(如C# display class或JS词法环境记录)
- PHP中use ($x)捕获的也是定义时$x的当前值或引用,后续外部$x变化不影响已创建的闭包,除非显式传引用
静态绑定意味着作用域链在函数对象创建时就固化
函数不是在调用时才“找爸爸”,而是在定义完成那一刻,其内部[[Environment]](JS)或__closure__(Python)或zend_op_array→scope(PHP)已明确指向特定的外层词法环境。
- 这意味着:把一个内层函数返回、传递到别处执行,它仍按原始定义位置查找变量,不会因新调用位置产生新作用域链
- JS中setTimeout(inner, 100)或Python中map(func, data)里的闭包,依然访问定义时的enclosing变量,而非调用时所在函数的局部变量
- 这种固化是闭包能保持状态的根本原因——外层函数执行结束,其词法环境未被销毁,因为仍有闭包引用它
常见误解与陷阱
容易出错的地方往往源于混淆“定义位置”和“调用位置”,或误以为变量查找会动态适配调用链。
- 循环中创建多个闭包却共享同一变量:for (let i = 0; i console.log(i), 0) } —— ES6的let每轮迭代新建绑定,输出0/1/2;但var声明下所有闭包共享i,最终输出3次3
- 误以为return的函数能访问调用它的函数的局部变量:function outer() { const y = 'outer'; return function inner() { console.log(y); }; } function caller() { const y = 'caller'; const fn = outer(); fn(); } —— 输出'outer',不是'caller'
- 在eval或with中打破静态性:这些机制会动态修改作用域链,破坏LEGB可预测性,应避免在闭包密集场景使用











