JavaScript变量生命周期本质是内存自动分配到释放的全过程,分三阶段:声明时栈/堆分配内存;运行时依可达性判断是否存活;引擎用标记-清除算法在适当时机回收不可达对象。

JavaScript 中变量的生命周期,本质是内存从分配到释放的全过程,核心围绕“谁在用、还能不能被找到、该不该留”这三个问题展开。它不是靠开发者手动控制,而是由引擎自动管理,但理解其逻辑能帮你避开内存泄漏、写出更健壮的代码。
变量诞生:内存分配发生在声明那一刻
你写 let obj = {name: 'Alice'} 或 const fn = () =>{} 的瞬间,引擎就做了两件事:
- 对基本类型(number、string、boolean、symbol、bigint、null、undefined),直接在栈内存中存值;
- 对引用类型(object、array、function、date、dom 节点等),在堆内存中开辟空间存实际数据,并在栈中存一个指向它的地址(类似“门牌号”)。
这个过程完全自动,无需 malloc 或 new(除了构造函数显式调用),但分配位置直接影响后续回收行为。
变量存活:可达性决定它是否“还在岗”
JS 不看变量有没有 var 或 let 声明,只看它是否还能被“找到”——这就是可达性(Reachability)原则:
立即学习“Java免费学习笔记(深入)”;
- 从根对象出发能顺藤摸瓜访问到的,就算“在职”,比如:
window上的全局变量、当前执行函数的参数和局部变量、调用栈里的活跃函数、DOM 树的根节点; - 一旦断开所有引用链(如
obj = null、函数执行完毕且无闭包捕获、事件监听器被移除),对象就变成“找不到的人”,标记为待回收; - 闭包是个特例:内部函数持续引用外部变量,会让那些本该随函数结束而消失的局部变量继续存活在堆中。
变量退场:垃圾回收不是实时,但很讲逻辑
现代 JS 引擎(V8、SpiderMonkey 等)主要用标记-清除(Mark-and-Sweep)算法,分两步走:
- 标记阶段:从根开始遍历,把所有能到达的对象打上“活跃”标签;
- 清除阶段:扫描整个堆,把没被打标的所有对象内存清掉,空出来复用。
它天然解决循环引用问题(比如 a.ref = b; b.ref = a),因为只要 a 和 b 都脱离了根链,哪怕彼此引用,也会被一并清除。回收不是每行代码后都发生,而是在内存压力上升或空闲时触发,所以有延迟但很可靠。
容易踩坑的地方:哪些操作会悄悄延长生命周期?
有些常见写法看似无害,实则让本该回收的对象一直挂着:
- 全局变量或挂载在
window上的引用(如window.cache = hugeData); - 未清理的定时器(
setInterval持有回调函数,回调又引用外部对象); - 未解绑的事件监听器(尤其 DOM 元素被移除后,监听器还挂在上面);
- 闭包过度捕获(比如在循环里创建函数却引用了循环变量
i,导致整个数组无法释放)。
这些不是语法错误,但会拖慢运行速度、增加内存占用,严重时引发页面卡顿甚至崩溃。


















