一、全局变量泄漏(高频考点)
问题场景 :未使用var/let/const
声明变量,或意外挂载到window
对象
// 错误示例(创建全局变量)
function initData() {
cache = new Array(1000000) // 隐式全局变量
}
// 正确方案(严格模式+局部变量)
'use strict'
function safeInit() {
const localCache = [] // 局部变量自动回收
}
防御建议:
- 所有文件开头添加
'use strict'
- 使用
const > let > var
声明变量 - 模块化开发避免全局污染
二、闭包引用泄漏(重点考察)
典型陷阱:闭包持有外部大对象引用且未主动释放
// 危险闭包(持有大数组)
function createClosure() {
const bigData = new Array(1000000).fill('data')
return () => {
console.log(bigData.length)
}
}
// 优化方案(主动释放引用)
function safeClosure() {
let tempData = new Array(1000000).fill('data')
const handler = () => {
console.log(tempData.length)
tempData = null // 使用后主动解除引用
}
return handler
}
开发注意:
- 避免在闭包中保留DOM元素引用
- 长生命周期闭包需手动清理资源
- 使用WeakMap替代常规Map存储临时数据
三、定时器/回调泄漏(常考场景)
高危操作 :未及时清理setInterval
或异步回调
// 泄漏示例(未清理定时器)
class PollingService {
constructor() {
this.timer = setInterval(() => {
this.fetchData() // this被长期持有
}, 1000)
}
// 忘记在销毁时clearInterval
}
// 正确实现(清理资源)
class SafePolling {
constructor() {
this.timer = null
}
start() {
this.timer = setInterval(() => {
// ...
}, 1000)
}
destroy() {
clearInterval(this.timer)
this.timer = null // 重要!
}
}
最佳实践:
- 组件销毁时执行清理操作(React的
useEffect
返回清理函数) - 使用
AbortController
取消未完成的fetch请求 - 避免在循环中创建匿名定时器
四、DOM引用泄漏(实际项目高频问题)
典型场景:缓存DOM元素引用后未清理
// 危险操作(保留已移除DOM的引用)
const elementsCache = {}
function renderList() {
const container = document.getElementById('list')
elementsCache.list = container // 缓存DOM引用
// 后续操作中移除了container但未清理cache
}
// 安全方案(弱引用+清理)
const weakMap = new WeakMap() // 使用WeakMap自动回收
function safeRender() {
const node = document.createElement('div')
weakMap.set(node, { data: 'temp' })
// 节点移除后自动释放内存
}
防御要点:
- 优先使用
WeakMap/WeakSet
存储DOM关联数据 - 移除节点后手动置空引用
element = null
- 避免在全局对象中缓存DOM元素
五、事件监听泄漏(易错点)
常见错误:动态元素绑定事件后未解绑
// 错误示例(未移除监听)
function initButton() {
const btn = document.createElement('button')
btn.addEventListener('click', handleClick)
document.body.appendChild(btn)
// 移除按钮但未移除监听
document.body.removeChild(btn)
}
// 正确实现(解绑+引用清理)
function safeInit() {
const btn = document.createElement('button')
const handler = () => console.log('clicked')
btn.addEventListener('click', handler)
document.body.appendChild(btn)
// 销毁时操作
const cleanup = () => {
btn.removeEventListener('click', handler)
btn.remove()
handler = null
}
return cleanup
}
工程化建议:
- 使用事件委托减少监听器数量
- 框架组件中在
unmount
生命周期执行清理 - 第三方库(如RxJS)需手动取消订阅
六、循环引用问题(特殊场景)
典型case:对象间相互引用导致无法回收
// 循环引用示例
function createCircularRef() {
const objA = { name: 'A' }
const objB = { name: 'B' }
objA.ref = objB
objB.ref = objA // 形成循环引用
return { objA, objB }
}
// 解决方案(打破引用链)
function safeCircular() {
const objA = { name: 'A' }
const objB = { name: 'B' }
objA.ref = objB
objB.ref = null // 单向引用
return { objA, objB }
}
优化策略:
- 使用
WeakRef
建立弱引用 - 手动断开不再需要的引用
- 复杂对象结构使用树形代替网状
检测与调试方案
-
Chrome DevTools:
- Performance面板录制内存变化
- Memory面板对比堆快照(查找Detached DOM节点)
-
监控指标:
// 实时监控内存 setInterval(() => { const used = performance.memory.usedJSHeapSize console.log(`内存使用:${(used / 1024 / 1024).toFixed(2)} MB`) }, 5000)
-
Lint工具配置:
// .eslintrc { "rules": { "no-undef": "error", "no-unused-vars": ["error", { "args": "after-used" }] } }
防御体系构建
-
代码规范:
- 强制使用严格模式
- 禁止隐式全局变量
- 要求资源释放注解
-
工程化方案:
// React组件示例 useEffect(() => { const timer = setInterval(...) return () => clearInterval(timer) // 清理函数 }, [])
-
自动化检测:
- 单元测试中加入内存断言
- E2E测试结合性能监控
- CI流程集成LeakCanary
通过以上方案组合使用,可有效降低内存泄漏风险。
实际开发中建议建立内存检查清单,在Code Review时重点核查闭包、全局变量、DOM操作等高风险点。