外观
前端有哪些常见的内存泄漏?
泄漏是指这块内存已经用不到了,却还有引用把它留住,垃圾回收收不走。闭包本身不是泄漏。全局变量、没卸掉的监听、定时器、被缓存的 DOM,才是常见来源。
原理
浏览器主堆上的对象,从根(全局、当前调用栈)出发走不到,就可以回收。意外的根会把整棵对象留住。
常见情况:
- 意外的全局变量,或挂在
window上的缓存从不删除。 setInterval、setTimeout的回调还握着组件状态,组件却已经卸载。- DOM 事件监听没有
removeEventListener。委托到稳定的父节点,或在卸载时取消。 - 脚本里保存了已经从文档移除的 DOM。元素上如果还有监听或数据属性,整棵子树都在。
- 脱离文档的节点被 Map 用对象当键且用的是
Map不是WeakMap。WeakMap的键是弱引用,键没了,记录可以一起走。
排查用开发者工具的 Memory 面板:拍两份堆快照,中间做一次「进入再离开」的操作,看离开之后哪些构造函数的数量没有掉下去。
追问
闭包算泄漏吗?
闭包让外层变量活得和内部函数一样久。如果内部函数的寿命是你期望的,这是功能。如果回调忘了取消,闭包只是把不该留下的数据一起留住了。
SPA 为什么更容易漏?
路由切走时,页面 DOM 没了,组件里注册的全局监听、订阅、定时器不一定跟着销毁。在卸载钩子里成对清理。
易错点
- 把所有闭包都说成泄漏。
console.log一个大对象时,开发者工具会保留它,快照看起来像泄漏。先关掉日志再测。- 移除 DOM 用
innerHTML = ""时,如果别处还握着子节点引用,子节点不会消失。