JavaScript 内存管理是如何工作的?

1. 开场白:看不见的房间管家

写 JS 的你我,几乎从不手动 malloc/free,但这不代表内存管理"不存在"。

JavaScript 引擎就像一位房间管家:

  • 你买东西(创建变量)→ 管家帮你找空位;
  • 你用完扔一边(解除引用)→ 管家扫进垃圾桶;
  • 你若一直占着茅坑不拉屎(内存泄漏)→ 管家也无可奈何。

理解管家的工作方式,才能让应用不爆内存、不卡顿、不 OOM。


2. 两张图看懂"东西"放哪儿

存储区域 放什么 特点 生命周期
Stack 栈 原始值(number、boolean、undefined、null、string、symbol、bigint) 固定大小、后进先出、超快 函数 return 即自动弹栈
Heap 堆 引用值(Object、Array、Function、Date、RegExp...) 动态大小、可按引用共享、稍慢 靠垃圾回收器(GC)稍后清理

代码示例

js 复制代码
let age  = 18;        // 栈
let user = {age};     // 栈里只存指针,实际对象在堆

3. 垃圾回收三部曲

现代引擎默认采用**"标记-清除"(Mark-and-Sweep)算法,配合"分代回收"**优化。

3.1 标记阶段(Mark)

从一组**根(Roots)**出发:

  • 当前调用栈里的局部变量;
  • 全局对象(window、globalThis);
  • 被 Chrome DevTools 断点 Hold 住的变量...

把所有能访问到的对象打上 "在用" 标签,其余都是 "垃圾"。

3.2 清除阶段(Sweep)

线性扫堆,把没标签的对象直接释放;

内存空隙由空闲链表 或按页压缩整理,避免碎片化。

3.3 分代优化(Generational GC)

V8 把堆再细分为:

  • 新生代 (1~8 M):短命对象,采用 Scavenge(复制回收),频率高、停顿短;
  • 老生代 :多次幸存的大对象、长寿命对象,采用 Mark-Sweep + Mark-Compact,频率低、停顿长。

实测:95% 的对象 20ms 内就死,分代后 GC 吞吐量提升 5~10 倍。


4. 四种常见内存泄漏与排查清单

泄漏场景 代码味道 修复要点
1. 意外全局变量 function f(){ leaky="oops" } 'use strict' + ESLint no-undef
2. 忘记清理定时器 setInterval(()=>{...},1000) const t=setInterval(...); 卸载时 clearInterval(t)
3. 闭包捕"大"不放 return ()=>console.log('hi') 却捕获了 new Array(1e6) 只把必要变量闭进去,或手动 largeData=null
4. 游离 DOM + 监听器 removeChild(node) 后仍保留 node 引用 node.remove(); node=null; 同时 off() 监听器

5. 开发者必备:Chrome DevTools 三板斧

  1. Performance Monitor
    实时折线图:JS heap size、DOM 节点、事件监听器数量。
  2. Heap Snapshot
    对比两次快照,按 "Retained Size" 排序,轻松找出"大胃王"。
  3. Allocation Timeline
    录制 30 秒用户操作,蓝色竖线表示新生代分配,峰值即潜在泄漏点。

6. 进阶:WeakMap / WeakRef 让 GC 更聪明

  • WeakMap 只保存弱引用,键对象被回收时,映射条目自动消失;
  • WeakRef 允许你观察对象是否已被 GC,而不阻止其被回收;
  • 适合缓存、DOM 元数据映射,不增加额外可达路径。

7. 一句话总结

JavaScript 帮你扫地,但别把垃圾藏到床底 ------

理解栈/堆、标记-清除、分代模型,善用 DevTools,远离四种泄漏,

你的页面才能常驻 60 FPS、不爆 256 M 低端机。


8. 延伸阅读

相关推荐
lpfasd1237 小时前
node-sass 停止维护!老项目迁移 Dart Sass 完整教程
前端·rust·sass
Data analyse4569 小时前
rnweb 回放数据量太大怎么办?分片存储与断点续传的实现思路
前端·网络·算法·数据分析
xiaogai_gai9 小时前
金蝶收料通知单查询策略实战教程:从源系统拉取到中间层落地的完整路径
java·前端·数据库
环境栈笔记10 小时前
指纹浏览器 WebRTC 怎么设置?从 ICE 候选、代理出口到功能验证
前端·人工智能·后端·自动化
赵大仁11 小时前
官网性能优化实战:LCP 3.2s → 2.1s、CLS 0.107 → 0,以及被我推翻的 6 个判断
前端·性能优化·cls·lcp·core web vitals
xiancai_xianyu11 小时前
Text2SQL落地生产库前,SQL要过哪三道闸
前端·数据库·sql·sql注入·text2sql·ai agent开发·企业级ai框架
小羊没烦恼!11 小时前
T-SQL查询进阶--理解SQL SERVER中的分区表
服务器·开发语言·前端·javascript·html
史瓦辛格11 小时前
给 DeepSeek Harness 写费用插件:把「这次会话花了多少钱」算准
前端·deepseek
星光不问赶路人11 小时前
Vue 3 + TypeScript 中 enum 的使用实践
前端·vue.js
鸥梨菌Honevid11 小时前
UVM 中 REQ 和 RSP 类型详解:从通信模型到 item_done 与 put_response
java·前端·servlet