为什么对象脱离作用域后内存没有及时回收?
我写了个Vue组件用setTimeout定时更新状态,但发现组件卸载后,Chrome内存 profi查看还是有大量对象残留。虽然在onUnmounted里用了clearTimeout,但用垃圾回收分析工具看对象状态还是“reachable”,这是怎么回事?
代码结构大概是这样的:
setup() {
const timer = ref(null)
const data = reactive({ count: 0 })
const start = () => {
timer.value = setTimeout(() => {
data.count++
start()
}, 1000)
}
onMounted(start)
onUnmounted(() => clearTimeout(timer.value))
return { data }
}
明明timer变量在组件作用域里,而且定时器应该形成了闭包引用,但按理说组件卸载后这些引用应该被切断才对啊?用WeakMap测试过对象确实不在作用域内了,可内存就是没释放……
start函数里的setTimeout会不断创建新的定时器,而每个定时器都持有了对外部data和start的引用,导致垃圾回收器认为它们还是“可达的”。试试这个:在onUnmounted里打断递归调用链。加个标志位
isUnmounted,组件卸载时设置为true,阻止递归调用继续创建新定时器。这样就能让对象真正脱离引用,内存才会被回收。timer.value,但setTimeout回调中的data和start依然被保留着,形成了一条可追踪的引用路径,导致 GC 无法回收这些对象。具体来说,这句:
创建了一个闭包,它捕获了
data和start。而start函数本身又会再次调用setTimeout,这就形成了一个「递归闭包链」,即使组件已经卸载,这个链依然存在,直到所有回调执行完毕。### 解决办法:
你需要在卸载时不仅清除定时器,还要「主动切断闭包中的引用」,比如设为 null:
或者更彻底一点,在回调中判断是否继续执行:
这样在组件卸载后,后续的回调直接返回,不再执行,也就不会再保留引用了。
闭包不是万恶之源,但你得知道它会捕获什么。GC 可没你想象中那么智能,有些引用你看着“没用了”,它可不认为可以回收。手动断引用,才是硬道理。