内部函数能引用外部变量的根本原因是词法作用域与静态环境绑定机制,函数创建时通过[[Environment]]属性持有所在位置词法环境的强引用,使被引用变量从栈迁移至堆并持续驻留。

内部函数能引用外部变量,根本原因不是“JavaScript 特别允许”,而是语言在设计时就通过词法作用域 + 静态环境绑定机制,让函数从诞生那一刻起就“自带快照”。这个快照不是复制变量值,而是持有一个指向原始词法环境的强引用。
函数创建时就绑定环境
每个函数对象在被定义(解析)时,引擎会为其内部属性 [[Environment]] 赋值——这个值直接指向它书写位置所在的词法环境(Lexical Environment),也就是外层函数或全局作用域的变量环境。这个绑定是静态的、不可更改的,和后续在哪里调用完全无关。
变量不会随外层函数销毁而消失
通常函数执行完,其局部变量会随执行上下文一起出栈销毁。但一旦有内部函数通过 [[Environment]] 持有了对外部变量的引用,V8 等引擎就会做两件事:
- 把被引用的变量从栈内存迁移至堆内存
- 让该词法环境持续驻留,哪怕外层函数早已返回
此时变量不是“被复制”,而是“被保留”;不是“还能访问”,而是“一直没被释放”。
闭包不是特殊语法,而是自然结果
你不需要写 makeClosure() 这样的 API —— 只要满足三个条件:嵌套函数、引用外部变量、该内部函数被导出使用,引擎就自动启用这套机制。它本质是作用域链的延续,不是额外加的功能,而是词法作用域模型的必然体现。
Python 中的对应机制
Python 同样依赖词法作用域,但多了一层显式保障:nonlocal 关键字明确告诉解释器“这个变量属于外层作用域,请不要新建同名局部变量”。没有它,赋值操作会触发局部变量声明,从而切断闭包行为。这也反向印证:闭包的关键不在“能否读”,而在“能否持续持有并修改”。

















