事件委托详解最新版

2,472 阅读4分钟

一、事件传播机制

想要深入了解委托,最好先深入理解事件传播机制:

我们把事件分为三个阶段:捕获阶段目标阶段冒泡阶段;

  1. 捕获阶段:当点击时,先经过捕获阶段,从最外层如html(这里不考虑兼容了)层层找到目标
  2. 目标阶段:经过目标阶段,响应事件
  3. 冒泡阶段:从点击目标往外层层触发相同的事件方法直到最外层(根节点)

触发当前元素的某一个事件(点击事件)行为,不仅当前元素事件行为触发,而且其祖先元素的相关事件行为也会依次被触发,这种机制就是 “事件的冒泡传播机制”

如图:

如果有不太理解的可以看这个小🌰哦😯:

比如某一天你在河边玩耍,,走着走着,看到一个小石子,哼,把小石子往河里一扔,河就泛起了层层波浪。这个时候就叫做冒泡

二、事件栗子(冒泡)

栗子

<div id="parent">
	<div id="child">按钮</div>
</div>
<script>
	const parent=document.getElementById('parent');
	const child=document.getElementById('child');
	parent.addEventListener('click',function(){
		console.log('parent');
	})
	child.addEventListener('click',function(){
		console.log('child');
	})
	//经过上面的分析很容易得出结果,小朋友们你们自己算哦
</script>

注意dom0和dom2事件机制不同;

dom0:在属性上挂载,同一个元素只能有一个点击事件,多个点击事件,后者会覆盖前者;

dom2:在EventTarget.prototype定义的,同一个元素多个点击事件不会覆盖,都会执行;原理是有一个统一的事件池;触发时,浏览器会把事件池中所有的按照存放顺序发放

这里就不多说啦啦啦啦,有一点偏离主题

三、委托的优点(经典例子)

1. 减少内存的消耗

因为绑定事件越多,浏览器内存占用越大,严重影响性能 还是举个🌰吧:

  • 有100条数据,100个li,给每个li都加事件,占用内存很大,所以,利用冒泡机制,在父元素上添加点击事件:如下
<ul id="ul">
</ul>
<script>
	const ul=document.getElementById('ul');
	for(let i=0;i<100;i++){
		let li=document.createElement('li');
		li.innerHTML=i;
		ul.appendChild(li);
	}
	ul.addEventListener('click',function(e){
		if(e.target.tagName==='UL') return;
		e.target.className=e.target.className.indexOf('color')===-1?'color':'';
	})//可自行执行;color是类名可以在style中添加自己喜欢的颜色,666
</script>

2. ajax的出现,局部刷新的盛行,导致每次加载完,都要重新绑定事件(这里就使用setTimeout异步代替了)

<ul id="ul">
	<li>666</li>
</ul>
<script>
	const ul=document.getElementById('ul');
	const lists=ul.getElementsByTagName('li');
	setTimeout(()=>{
		for(let i=0;i<100;i++){
			let li=document.createElement('li');
			li.innerHTML=i;
			ul.appendChild(li);
		}
	},400)
	for(let i=0;i<lists.length;i++){
		lists[i].onclick=function(){
			alert(i);
		}
	}
	//结果只点击666弹窗;怎么解决;大家动动脑子吧;相信你们是最棒的
</script>

四、委托的局限性

  1. 比如 focus、blur 之类的事件本身没有事件冒泡机制,所以无法委托;
  2. mousemove、mouseout这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的;

五、使用委托的注意项(可以叫应用项)

  1. 只在必须的地方,使用事件委托,比如:ajax的局部刷新区域
  2. 尽量的减少绑定的层级,并且不在body元素上,进行绑定;(事件委托的原理离不开DOM的查找;而浏览器太多层级的查找非常耗性能
  3. 减少绑定的次数,如果可以,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行分发。

六、经典面试题

mouseeneter 和 mouseover 的区别?

inner.onmouseenter = function () {
    console.log('inner enter');
};
outer.onmouseenter = function () {
    console.log('outer enter');
};
inner.onmouseleave = function () {
    console.log('inner leave');
};
outer.onmouseleave = function () {
    console.log('outer leave');
};

讲解:

  1. over属于滑过(覆盖)事件,从父元素进入到子元素,属于离开了父元素,会触发父元素的out,触发子元素的over;enter属于进入,从父元素进入子元素,并不算离开父元素,不会触发父元素的leave,触发子元素的enter
  2. enter和leave阻止了事件的冒泡传播,而over和out还存在冒泡传播的

所以对于父元素嵌套子元素这种情况,使用OVER会发生很多不愿意操作的事情,此时我们使用ENTER会更加简单,操作方便,所以真实项目中ENTER的使用会比OVER多

此文章中未涉及到兼容性,有时间再加8⃣️