在 Vue 中移除原生 JavaScript 事件监听器(即通过 addEventListener 绑定的事件),核心原则与原生 JS 一致:在组件合适的生命周期钩子中,使用 removeEventListener 移除,且确保事件类型、回调函数引用、捕获阶段参数与绑定时代完全匹配。以下是针对 Vue 3 和 Vue 2 的具体实现方法及避坑指南:
移除原生事件监听器的关键是 “参数三匹配”:
- 事件类型(如
click、scroll)一致;
- 回调函数引用一致(不能用匿名函数);
- 捕获阶段参数(
useCapture,默认 false)一致。
错误示例(匿名函数无法移除):
boxRef.value.addEventListener('click', () => console.log('点击'));
boxRef.value.removeEventListener('click', () => console.log('点击'));
正确示例(具名函数确保引用一致):
function handleClick() {
console.log('点击');
}
boxRef.value.addEventListener('click', handleClick);
boxRef.value.removeEventListener('click', handleClick);
Vue 3 中需通过 ref 获取 DOM 元素引用,在组件挂载时绑定事件,在卸载前(onUnmounted)移除,确保组件销毁时事件被清理。
<template>
<div ref="box" class="box">点击或移动鼠标</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
// 1. 获取 DOM 引用
const box = ref(null);
// 2. 定义具名回调函数(确保引用唯一)
function handleClick() {
console.log('盒子被点击');
}
function handleMouseMove(e) {
console.log('鼠标位置:', e.clientX, e.clientY);
}
// 3. 组件挂载时绑定事件
onMounted(() => {
if (box.value) {
box.value.addEventListener('click', handleClick); // 绑定 click
box.value.addEventListener('mousemove', handleMouseMove); // 绑定 mousemove
}
});
// 4. 组件卸载时移除所有事件(关键步骤)
onUnmounted(() => {
if (box.value) {
box.value.removeEventListener('click', handleClick);
box.value.removeEventListener('mousemove', handleMouseMove);
}
});
</script>
<style>
.box { width: 200px; height: 200px; background: #eee; }
</style>
若绑定事件时使用了捕获阶段(addEventListener 第三个参数为 true),移除时必须传入相同参数:
onMounted(() => {
box.value.addEventListener('click', handleClick, true);
});
onUnmounted(() => {
box.value.removeEventListener('click', handleClick, true);
});
若组件内绑定了多个原生事件,可通过数组批量管理,遍历移除:
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
const box = ref(null);
// 存储所有原生事件配置(类型 + 回调 + 捕获阶段)
const domEvents = [
{ type: 'click', handler: handleClick, useCapture: false },
{ type: 'mousemove', handler: handleMouseMove, useCapture: false },
{ type: 'mouseleave', handler: handleMouseLeave, useCapture: true }
];
function handleClick() { /* ... */ }
function handleMouseMove() { /* ... */ }
function handleMouseLeave() { /* ... */ }
onMounted(() => {
if (box.value) {
domEvents.forEach(({ type, handler, useCapture }) => {
box.value.addEventListener(type, handler, useCapture);
});
}
});
onUnmounted(() => {
if (box.value) {
domEvents.forEach(({ type, handler, useCapture }) => {
box.value.removeEventListener(type, handler, useCapture);
});
}
});
</script>
Vue 2 中通过 $refs 获取 DOM 元素,在 mounted 中绑定事件,在 beforeDestroy 中移除,回调函数定义在 methods 中确保引用稳定。
<template>
<div ref="box" class="box">点击我</div>
</template>
<script>
export default {
methods: {
// 回调函数定义在 methods 中,引用唯一
handleClick() {
console.log('盒子被点击');
}
},
mounted() {
// 组件挂载后绑定事件
this.$refs.box.addEventListener('click', this.handleClick);
},
beforeDestroy() {
// 组件销毁前移除事件(关键)
this.$refs.box.removeEventListener('click', this.handleClick);
}
};
</script>
若 DOM 元素通过 v-if 控制显示 / 隐藏,需在元素销毁前(如 beforeDestroy)判断元素是否存在,避免报错:
beforeDestroy() {
if (this.$refs.box) {
this.$refs.box.removeEventListener('click', this.handleClick);
}
}
- 问题:绑定匿名函数或动态创建的函数,导致
removeEventListener 找不到相同引用。
- 解决方案:始终使用具名函数(如
function handleClick())或在组件实例上保存函数引用(如 Vue 2 的 methods、Vue 3 的 setup 内定义)。
- 问题:绑定事件时用了
useCapture: true,移除时未传入,导致移除失败。
- 解决方案:移除时严格保持
useCapture 参数与绑定一致。
- 问题:组件内 DOM 通过
v-if 销毁,或组件卸载时 DOM 已被移除,此时调用 removeEventListener 会报错。
- 解决方案:移除前先判断 DOM 元素是否存在(如
if (box.value))。
- 问题:通过 Vue 的
@click 绑定的事件,无需手动移除(Vue 会自动清理),但如果同时用 addEventListener 绑定了相同事件,需手动移除。
<template>
<!-- Vue 指令绑定的事件:自动移除 -->
<button @click="vueClick">Vue 指令点击</button>
<!-- 原生 addEventListener 绑定的事件:需手动移除 -->
<button ref="nativeBtn">原生事件点击</button>
</template>
- 绑定阶段:用
ref(Vue 3)/$refs(Vue 2)获取 DOM 引用,使用具名函数通过 addEventListener 绑定事件,记录事件类型、回调、捕获阶段参数。
- 移除阶段:在组件卸载钩子(
onUnmounted Vue 3 /beforeDestroy Vue 2)中,通过 removeEventListener 移除事件,确保参数与绑定完全一致。
- 兜底处理:移除前判断 DOM 是否存在,避免报错;批量事件通过数组管理,遍历移除提高效率。
遵循以上步骤,可彻底避免原生事件监听器残留导致的内存泄漏问题。 |