调用栈、作用域链与闭包是JavaScript运行机制的三层支撑:调用栈管理函数执行时序(时间维度),作用域链提供变量查找路径(空间维度),闭包则是作用域链在函数脱离原始环境后仍被持有的生命力延伸。

调用栈(Call Stack)是 JavaScript 运行时管理函数执行顺序的核心机制,它决定了“谁在什么时候调用谁”“变量从哪找”“为什么闭包能记住老变量”。理解它,不是背概念,而是看清代码运行的脉络。
函数调用触发压栈与出栈
每次函数被调用,引擎就在调用栈顶部“压入”一个执行上下文(Execution Context),包含该函数的变量环境、词法环境和 this 绑定。函数执行完毕,这个上下文立即“弹出”,内存释放。
- 同步函数调用是严格后进先出:funA() 调用 funB(),栈里先有 A 的上下文,再压入 B;B 执行完先出栈,A 再继续或结束
- 递归调用会连续压栈,若无终止条件,栈溢出(RangeError: Maximum call stack size exceeded)
- 异步回调(如 setTimeout 回调)不直接入调用栈,而是等主线程空闲、由事件循环推入,此时其执行上下文是全新创建的
作用域决定变量查找路径,而非调用位置
JavaScript 采用词法作用域——变量在哪定义的,就归哪个作用域管。查找变量时,引擎沿着当前执行上下文的词法环境链(本质是作用域链)逐级向上找,跟函数“从哪被调用”完全无关。
- 全局作用域 → 函数作用域 → 块级作用域(let/const):一层套一层,嵌套关系在写代码时就固定了
- var 声明会提升,但只提升声明,不提升赋值;let/const 有暂时性死区,未到声明行前访问会报错
- 函数内部访问变量,先查自己作用域,找不到就查外层函数作用域,最后查全局,找不到就 ReferenceError
闭包是作用域链的“快照留存”
当一个函数作为返回值被传出,或被赋给外部变量,而它又引用了外层函数的局部变量,JS 引擎就会保留那条完整的作用域链——即使外层函数早已执行完毕、其执行上下文本该被销毁。这就是闭包。
- 闭包的关键不在“嵌套”,而在“内部函数被带到定义它的词法环境之外使用”
- 典型例子:createCounter() 返回一个函数,该函数持续访问并修改外层的 count 变量,count 不会被回收
- for 循环中用 var + setTimeout 输出全是 5,是因为所有回调共享同一个 i 变量;用 IIFE 或 let 声明,就是为每次迭代创建独立作用域,形成多个闭包,各自记住自己的 i
调用栈、作用域链与闭包的关系
三者不是并列概念,而是层层支撑:
- 调用栈 管理函数“何时执行、谁在执行”——是运行时的时间维度
- 作用域链 是每个执行上下文内部的“变量寻址地图”——是静态定义的空间维度
- 闭包 是作用域链在函数脱离原始执行环境后仍被持有的一种表现——是作用域链的生命力延伸


















