了解事件委托前先了解事件冒泡
什么是事件冒泡
当一个元素接收到事件的时候,会把他接收到的事件传给父级,一直传到window (注意这里传递的仅仅是事件 并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件 也不会有什么表现 但事件确实传递了。)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id='parentID' onclick="parentfunc()">
<div id='childID' onclick="childfunc()">你好</div>
</div>
<script>
function parentfunc() {
alert('父级事件触发了!')
}
function childfunc(){
alert('子级事件触发了!')
}
</script>
</body>
</html>
以上代码,触发了childID的点击事件,但是他的父级也会被触发,这个情况就是冒泡排序,那么如何阻止冒泡排序呢,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id='parentID' onclick="parentfunc()">
<div id='childID' onclick="childfunc()">你好</div>
</div>
<script>
function parentfunc() {
alert('父级事件触发了!')
}
function childfunc(){
alert('子级事件触发了!')
stopBubble();
}
function stopBubble(e) {
//如果提供了事件对象,则这是一个非IE浏览器
if ( e && e.stopPropagation ){
//因此它支持W3C的stopPropagation()方法
e.stopPropagation();
}else{
//否则,我们需要使用IE的方式来取消事件冒泡
window.event.cancelBubble = true;
}
}
</script>
</body>
</html>
阻止冒泡排序有两种情况,如下:
标准的W3C 方式:e.stopPropagation();这里的stopPropagation是标准的事件对象的一个方法,调用即可
非标准的IE方式:ev.cancelBubble=true; 这里的cancelBubble是 IE事件对象的属性,设为true就可以了
事件委托
概念
事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了。
为什么要用事件委托:
一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
-
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;
-
每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了(内存不够用,是硬伤,哈哈),比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,那只能说呵呵了,如果用事件委托,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好。
好处:
提高性能,减少了事件绑定,从而减少内存占用
事件委托的原理:
事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
案例1
需要点击每个a,来。弹出他们的内容
<div id="div3">
<a href="#">a1</a><br>
<a href="#">a2</a><br>
<a href="#">a3</a><br>
<a href="#">a4</a><br>
<button>加载更多...</button>
</div>
// 封装通用的事件绑定函数
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
//获取父元素
const fu = document.getElementById('div3')
bindEvent(fu, 'click', function (event) {
// console.log(event.target) // 获取触发的元素
let target=event.target
event.preventDefault() // 阻止默认行为
//过滤符合条件的子元素,主要是过滤掉 加载更多
if(target.nodeName.toLowerCase()==="A"){
alert(target.innerHTML)
}
})
我们可以用事件委托的方式来实现这样的效果
-
这样我们就可以做到很多个a链接上的点击事件都添加到父元素div上。
-
当然还要排除掉不参加事件委托的子元素。
应用场景 在vue中事件委托
我们经常遇到vue中v-for一个列表,列表的每一项都绑定了@click处理事件。我们都知道绑定这么多监听,从性能方面来说是不太好的。那我们我们可以通过把每个item的click事件委托给父元素的形式来实现。
获取item元素中title值为edit的id
//html
<table @click="edit">
<tr v-for="item in list">
<td>{{item.name}}</td>
...
<td>
<button :data-id="item.id" title="eidt">编辑</button>
</td>
</tr>
</table>
//js
edit (event){
if(event.target.title == "edit"){ //如果点击到了edit
let id = evenr.target.dataset.id;
//拿着id参数执行着相关的操作
this.$router.push({path:'/detail',query:{id:id}})
}
}
总结
那什么样的事件可以用事件委托,什么样的事件不可以用呢?
-
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
-
值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
-
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。