Code前端首页关于Code前端联系我们

Vue2 中什么时候需要用 addEventListener 而不是 v-on?

terry 9小时前 阅读数 10 #Vue
文章标签 Vue2;事件绑定

不少刚接触 Vue2 的同学,在处理事件时会疑惑:明明有 v-on 指令,为啥还要学 addEventListener?什么时候得用原生事件绑定?绑定后又该咋避免内存泄漏?今天就围绕 Vue2 里的 addEventListener 把这些问题掰碎了讲。

首先得明白 v-on 和 addEventListener 的本质区别,v-on 是 Vue 封装的指令,处理 DOM 事件时会做事件委托(比如列表里的按钮,Vue 会在父元素上绑一次事件,利用冒泡减少性能消耗),还能处理组件间的自定义事件(子组件 $emit 触发父组件方法),但碰到这几种情况,v-on 就不够用了:

  • 和第三方库交互时:比如集成地图 SDK(百度/高德地图)、视频播放器(Video.js),这些库的 DOM 元素不是 Vue 直接管理的,得用原生事件绑定才能监听到它们的点击、缩放等事件。
  • 给非 Vue 渲染的 DOM 绑事件:比如页面里有个通过原生 JS 动态创建的弹窗,或者浏览器插件注入的元素,Vue 的 v-on 够不着,只能用 addEventListener 硬绑。
  • 需要控制事件阶段:addEventListener 第三个参数可以设置 useCapture(是否在捕获阶段触发事件),v-on 只能在冒泡阶段处理事件,比如要拦截子元素的点击事件,阻止它冒泡到父元素,就需要在捕获阶段绑事件。
  • 做全局事件监听:比如监听浏览器的 resizescroll,或者给 document/window 绑自定义事件实现跨组件通信,这些场景用 addEventListener 更灵活。

Vue2 里怎么正确使用 addEventListener 绑定事件?

核心逻辑是“在 DOM 渲染完成后绑定,用组件方法当回调,方便后续解绑”,具体步骤看例子:

步骤 1:选对生命周期钩子

Vue 组件的 mounted 钩子执行时,DOM 已经渲染完成,所以绑定事件一般写在 mounted 里,比如给页面上的 canvas 绑点击事件:

export default {
  mounted() {
    // 通过 $refs 获取 DOM(前提是给 canvas 加 ref="canvas")
    this.$refs.canvas.addEventListener('click', this.handleCanvasClick)
  },
  methods: {
    handleCanvasClick(e) {
      console.log('canvas 被点击了', e)
      // 这里的 this 指向 Vue 实例,能直接访问 data、props 等
    }
  }
}

要是 DOM 是动态生成的(v-for 渲染的列表),得确保绑事件时元素已经存在,如果拿不准,也可以用 this.$nextTick 延迟执行绑定逻辑,保证 DOM 更新完成。

步骤 2:处理 this 指向(可选)

addEventListener 的回调函数里,this 默认指向绑定事件的 DOM 元素(比如上面的 canvas),但 Vue 方法里的 this 默认指向组件实例,所以直接把组件方法(如 handleCanvasClick)当回调,this 就还是 Vue 实例,不用额外 bind 或用箭头函数。

但如果用匿名函数当回调(addEventListener('click', () => { ... })),this 虽然能指向 Vue 实例,但后续解绑会失败(因为匿名函数没法精准匹配),所以一定要用具名函数当回调

addEventListener 绑定的事件怎么解绑?为啥必须解绑?

不解绑会出大问题:组件销毁后,事件监听器还挂在 DOM 上,导致内存泄漏(浏览器没法回收这些资源),页面越用越卡,所以解绑是必做步骤,关键是“在组件销毁前解绑,且参数和绑定完全一致”

步骤:在 beforeDestroy 钩子解绑

Vue 的 beforeDestroy 钩子会在组件销毁前执行,这里写解绑逻辑最安全,延续上面 canvas 的例子:

export default {
  mounted() {
    this.$refs.canvas.addEventListener('click', this.handleCanvasClick)
  },
  methods: {
    handleCanvasClick(e) { ... }
  },
  beforeDestroy() {
    this.$refs.canvas.removeEventListener('click', this.handleCanvasClick)
  }
}

注意这几点:

  • 事件名、回调函数、useCapture(第三个参数)必须和绑定时候完全一样,比如绑定的时候用了捕获阶段(addEventListener('click', fn, true)),解绑时第三个参数也得传 true
  • 回调必须是同一个函数引用,如果绑定用的是匿名函数,解绑时找不到对应的函数,等于白写,所以一定要用组件 methods 里的具名函数。
  • 如果绑定的是 window/document 这类全局对象,解绑时也要用同样的对象,比如绑了 window.addEventListener('resize', fn),解绑得用 window.removeEventListener(...)

addEventListener 和 v-on 处理事件有啥本质区别?

很多同学觉得“都是绑事件,用哪个都行”,但两者底层逻辑差很多,选错了容易踩坑:

对比维度 addEventListener v-on
底层实现 原生 JS 方法,直接绑在 DOM 元素上 Vue 封装的指令,DOM 事件会做事件委托(减少重复绑定)
事件类型 只能处理DOM 原生事件(click、scroll 等) 既能处理 DOM 原生事件,也能处理组件自定义事件(子组件 $emit 触发)
性能表现 给大量元素绑同类事件时(比如列表里每个按钮绑 click),会重复绑定,性能差 DOM 事件用事件委托,只在父元素绑一次,性能更优
事件阶段控制 能设置捕获阶段(useCapture: true) 只能在冒泡阶段触发事件,无法控制阶段
解绑逻辑 必须手动调用 removeEventListener,否则内存泄漏 Vue 自动管理解绑(组件销毁时,v-on 绑的事件会自动移除)

简单说:日常写 Vue 组件,优先用 v-on;碰到第三方库、全局事件、捕获阶段需求,再用 addEventListener。

在 Vue2 组件通信里,addEventListener 能怎么玩?

Vue2 里组件通信方式很多(props/$emit、事件总线、Vuex 等),但 addEventListener 能实现更“原生”的跨组件通信,适合简单场景,举个兄弟组件通信的例子:

场景:组件 A 点击按钮,组件 B 接收数据

思路是用 document 当“事件中转站”,A 触发自定义事件,B 监听这个事件。

// 组件 A(触发方)
<template><button @click="sendMsg">发消息</button></template>
export default {
  methods: {
    sendMsg() {
      // 自定义事件名:brother-event,传参用 detail
      const event = new CustomEvent('brother-event', { 
        detail: { msg: '吃饭了没?' } 
      })
      document.dispatchEvent(event) // 触发全局事件
    }
  }
}
<p>// 组件 B(接收方)
export default {
mounted() {
// 监听 document 上的 brother-event 事件
document.addEventListener('brother-event', this.handleMsg)
},
methods: {
handleMsg(e) {
console.log('收到消息:', e.detail.msg) // 吃饭了没?
}
},
beforeDestroy() {
// 销毁前解绑,避免内存泄漏
document.removeEventListener('brother-event', this.handleMsg)
}
}

这种方式不用依赖 Vuex 或事件总线插件,纯原生 JS + Vue 生命周期就能实现跨组件通信,适合小项目里简单的交互逻辑。

用 addEventListener 常见的坑有哪些?怎么避?

踩过这些坑的同学肯定懂那种“代码没报错,但事件不触发/重复触发/内存爆炸”的绝望,总结几个高频坑和解决方案:

坑 1:忘记解绑,内存泄漏

表现:组件销毁后,事件还在触发,浏览器内存占用越来越高。
原因:addEventListener 绑的事件不会被 Vue 自动销毁,必须手动 removeEventListener。
解法:在 beforeDestroy(或 unmounted,Vue3 里)里写解绑逻辑,且参数和绑定完全一致。

坑 2:事件重复绑定

表现:点一次按钮,回调执行多次(mounted 里的绑定逻辑被多次执行)。
原因:绑定逻辑所在的钩子(如 mounted)被多次触发(比如组件用 keep-alive 缓存,activated 会重复执行,但 mounted 只执行一次?不,keep-alive 时 mounted 只执行一次,activated 每次激活执行,所以如果在 activated 里绑事件,没在 deactivated 里解绑,就会重复绑)。
解法:确保“绑定 - 解绑”成对出现,比如在 activated 里绑,deactivated 里解绑;或者在绑定前判断是否已绑定(用标志位,isEventBound)。

坑 3:事件类型拼写错误

表现:事件完全不触发,控制台没报错。
原因:把 click 写成 clik,把 resize 写成 rezie 等。
解法:绑定前仔细检查事件名,或者把事件名存在变量里(const eventType = 'click'),避免手误。

坑 4:捕获阶段和冒泡阶段搞混

表现:想拦截子元素事件,结果没生效;或者解绑时因为阶段参数不一致,解绑失败。
原因:addEventListener 第三个参数 useCapture 控制事件阶段,绑定和解绑时参数必须一致,比如绑定用了 true(捕获阶段),解绑时没传 true,就会解绑失败。
解法:明确自己需要的事件阶段,绑定和解绑时第三个参数保持一致。

坑 5:动态 DOM 绑定失败

表现:给 v-for 生成的元素绑事件,结果拿不到 DOM。
原因:v-for 渲染的 DOM 是动态生成的,mounted 执行时可能还没渲染完(尤其是异步数据渲染的列表)。
解法:用 this.$nextTick 延迟绑定,确保 DOM 渲染完成;或者用 MutationObserver 监听 DOM 变化后再绑事件。

把这些坑避开,addEventListener 在 Vue2 里就能用得顺风顺水啦~

Vue2 里的 addEventListener 是 v-on 的“补位选手”——日常开发优先用 v-on 省心,但碰到第三方库、全局事件、捕获阶段这些特殊场景,就得靠 addEventListener 上场,核心是掌握绑定时机、解绑逻辑、场景边界,再避开那些常见的小坑,就能用它解决很多复杂交互问题啦~要是你在实际开发中还有其他疑问,评论区随时聊~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门