人人都会AI编程

30.4 组件销毁后定时器、事件监听的内存泄漏

更新时间:2026-07-09

内存泄漏的典型症状:页面用久了越来越卡,切换路由后旧页面的一些逻辑仍在后台偷偷运行。在 Vue 组件中,最常见的泄漏来源就是忘记清理定时器全局事件监听。组件销毁时,这些回调依然持有对组件作用域的引用,导致组件实例无法被垃圾回收,同时继续执行无谓的逻辑。

错误示例:定时器未清除

<script setup>
import { ref, onMounted } from 'vue'

const count = ref(0)

onMounted(() => {
  // 组件挂载后启动一个每秒自增的定时器
  setInterval(() => {
    count.value++
  }, 1000)
})
</script>

当这个组件被卸载时,setInterval 仍在运行。它引用了 count,导致整个组件作用域无法释放。如果用户反复进入 / 离开该页面,会不断堆积新的定时器,最终页面交互变得卡顿甚至崩溃。

错误示例:全局事件监听未移除

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  const handleResize = () => {
    console.log(window.innerWidth)
  }
  window.addEventListener('resize', handleResize)
})
</script>

组件销毁后,handleResize 依然绑定在 window 上。每次窗口大小变化,它都会执行,并试图访问已销毁组件中的上下文(如果函数内使用了响应式变量,甚至会报错)。

解决方案:利用生命周期清理

Vue 3 组合式 API 中,可以在 onUnmounted 钩子中手动清除:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const count = ref(0)
let timer = null

onMounted(() => {
  timer = setInterval(() => {
    count.value++
  }, 1000)

  const handleResize = () => {
    console.log(window.innerWidth)
  }
  window.addEventListener('resize', handleResize)

  // 将清理逻辑注册到卸载钩子
  onUnmounted(() => {
    clearInterval(timer)
    window.removeEventListener('resize', handleResize)
  })
})
</script>

选项式 API 中,对应使用 beforeUnmountunmounted(Vue 2 对应 beforeDestroy / destroyed)。

更优雅的自动化清理

定时器可以借助 onUnmounted,也可以使用 VueUse 的 useIntervalFn(自动清理)。事件监听推荐使用 useEventListener(VueUse)或在 onMounted 中绑定后,用同一函数引用在 onUnmounted 中移除。

watch / watchEffect 的副作用清理

watchwatchEffect 内部如果开启了定时器或监听了外部事件,可以在 onCleanup 回调中处理:

<script setup>
import { watch, ref, onUnmounted } from 'vue'

const keyword = ref('')

watch(keyword, (newVal, oldVal, onCleanup) => {
  const timer = setTimeout(() => {
    console.log('搜索:', newVal)
  }, 300)

  // 下一次 watch 触发或组件卸载时会自动执行
  onCleanup(() => clearTimeout(timer))
})
</script>

这种方式可以避免频繁触发时的竞态问题,同时组件销毁时清理也会自动执行。

排查内存泄漏的实用方法

  • 打开浏览器 DevTools 的 Performance MonitorMemory 面板,反复进入/离开某个页面,观察 JS 堆内存是否持续增长。
  • 检查控制台是否有重复的日志输出(例如每隔一秒的数字递增,离开页面后仍未停止)。
  • 养成一个习惯:只要写了 setInterval / addEventListener / WebSocket / 第三方库实例化,立刻回退一步,确认在卸载时是否有对应的清理逻辑。

这条规则很简单,却能在项目中避免大量难以复现的“时间久了就卡”的幽灵问题。