一、事件委托(事件代理)
将原本需要绑定在子元素上的事件监听器委托在父元素上,让父元素充当事件监听的职务。
利用事件冒泡的特性,在父节点上响应事件,而不是在子节点上响应事件的技术。它能够改善性能,因为只需要在父元素上设置一次事件监听器,就可以管理同一类型的所有子元素的事件。
即事件从最深的节点开始,逐步向上传播。在事件冒泡过程中,父元素会捕获到子元素的事件,并进行分析。通过查看event对象的属性,可以确定是哪个子元素的事件,从而执行相应的处理逻辑。
使用事件委托能够避免对每个子元素单独设置事件监听器,降低了与DOM交互的次数,提高了页面的整体运行性能。同时,事件委托也具有更高的灵活性和可维护性,不需要操作大量的DOM元素。
在Vue中,可以利用v-on指令或@符号来绑定事件监听器,并在父元素上设置事件委托。例如,可以在父元素上设置一个click事件监听器,然后在子元素上绑定一个click事件,通过事件委托实现父元素对子元素事件的响应。
1、事件委托的优点
- 节省内存(dom与js的关联),减少事件的注册
- 增加子元素也无需再注册事件
2、事件委托的缺点
获取绑定的节点数据会相对麻烦一点
3、为什么要使用事件委托
在日常开发中,很经常我们会遇到个问题,就是在长列表数据较多的时候,而又需要对子元素注册一些事件(如onClick),就会造成比较大的内存开支,很耗费性能,也可能会造成页面卡顿等等;
所以可以通过在父元素上添加@click监听,而不是在子元素上注册事件;
如果数据量比较少,就可忽略不计;
此外,事件处理程序需要与 DOM 节点进行交互,访问 DOM 的次数越多,引起浏览器重绘和重排的次数也就越多,从而影响页面的性能。
4、事件委托实现原理
事件委托是利用事件的冒泡原理来实现的,大致可以分为三个步骤:
- 确定要添加事件元素的父级元素;
- 给父元素定义事件,监听子元素的冒泡事件;
- 使用 event.target 来定位触发事件冒泡的子元素。
5、DOM事件流
事件流描述的是从页面中接收事件的顺序。事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流。
比如:我们给页面中的一个div注册了单击事件,当你单击了div时,也就单击了body,单击了html,单击了document。
DOM 事件流会经历3个阶段:
- 捕获阶段:事件从文档的根节点流向目标对象。
事件从最不精确的对象(document 对象)开始触发,然后到最精确(也可以在窗口级别捕获事件,不过必须由开发人员特别指定),与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
- 当前目标阶段:在目标对象上被触发。
- 冒泡阶段:回溯到文档的根节点。
微软提出了名为事件冒泡的事件流。事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。
6、事件委托的实现方法
1、vue写法
1.1、html代码
<div id="app"> <div id="event-agent" @click="eventAgent"> <p v-for="(item, index) in list" :key="index" :data-name="item.name" :data-index="index">{{item.name}}</p> </div> </div>
> 获取节点参数 (data-index、data-name),则在 $event.target.dataset { index: 'xxx', name: 'xxx' } 中取值
1.2、js代码
data() { return { list: [ { id: 1, name: 'kmj1'}, { id: 2, name: 'kmj2'}, { id: 3, name: 'kmj3'}, { id: 4, name: 'kmj4'} ] } }, methods: { // 事件委托 eventAgent(e) { const target = e.target; console.log(target ) // 注意 e.target.nodeName 的元素名是大写的 if (target && target.nodeName === "P") { const dataset = target .dataset; console.log('$event.target.dataset : ' dataset ); // $event.target.dataset : { name: 'xxx', index: 'xxx' } } } }
2、原生的写法其实也差不多:
2.1、html代码
<div id="event-agent"> <p :data-name="Item1" :data-index="0">Item 1</p> <p :data-name="Item2" :data-index="1">Item 2</p> <p :data-name="Item3" :data-index="2">Item 3</p> <p :data-name="Item4" :data-index="3">Item 4</p> <p :data-name="Item5" :data-index="4">Item 5</p> <p :data-name="Item6" :data-index="5">Item 6</p> </div>
2.2、js代码
1.document.getElementById( "event-agent").onclick = function(event){ // 兼容Ie的写法 event = event || window.event; var target = event.target || event.srcElement; // 注意 e.target.nodeName 的元素名是大写的 if (target && target.nodeName === "P") { const dataset = target .dataset; console.log('$event.target.dataset : ' dataset ); // $event.target.dataset : { name: 'xxx', index: 'xxx' } } }; // 也可以用这种方式,其实都差不多的: // 冒泡阶段处理程序 document.getElementById( "event-agent").addEventListener( "click", (e) => {}, false); // 捕获阶段处理程序 document.getElementById( "event-agent").addEventListener( "click", (e) => {}, true);
二、阻止事件冒泡
W3C的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true。 stopPropagation是事件的一个方法,作用是阻止目标元素的事件冒泡,但是不会组织默认行为。
function stopBubble(e) { //如果提供了事件对象,则这是一个非IE浏览器 if ( e && e.stopPropagation ) //因此它支持W3C的stopPropagation()方法 e.stopPropagation(); else //否则,我们需要使用IE的方式来取消事件冒泡 window.event.cancelBubble = true; }
三、取消默认事件
W3C的方法是e.preventDefault(),IE则是使用e.returnValue = false。 preventDefault()是事件对象(Event)的一个方法,作用是取消一个目标元素的默认行为。既然是默认行为,那么元素必须有默认行为才能被取消,如果元素本身就没有默认行为,调用自然就无效了。什么元素有默认行为呢?如链接\<a href="">,提交按钮<input type=”submit”>等,这些都有默认行为。
function preventDefault(e){ if(e && e.preventDefault){ e.preventDefault(); } else{ window.event.returnValue = false; } return false; }
四、总结
要使用事件委托,需要保证事件能够发生冒泡,适合使用事件委托的事件有 click、mousedown、mouseup、keydown、keyup、keypress 等。需要注意的是,虽然 mouseover 和 mouseout 事件也会发生事件冒泡,但处理起来非常麻烦,所以不推荐在 mouseover 和 mouseout 事件中使用事件委托。
另外,对于不会发生事件冒泡的事件(例如 load、unload、abort、focus、blur 等),则无法使用事件委托。