一、事件传播机制
想要深入了解委托,最好先深入理解事件传播机制:
我们把事件分为三个阶段:
捕获阶段
、目标阶段
、冒泡阶段
;
- 捕获阶段:当点击时,先经过捕获阶段,从最外层如html(这里不考虑兼容了)层层找到目标
- 目标阶段:经过目标阶段,响应事件
- 冒泡阶段:从点击目标往外层层触发相同的事件方法直到最外层(根节点)
触发当前元素的某一个事件(点击事件)行为,不仅当前元素事件行为触发,而且其祖先元素的相关事件行为也会依次被触发,这种机制就是
“事件的冒泡传播机制”
如图:
如果有不太理解的可以看这个小🌰哦😯:
比如某一天你在河边玩耍,,走着走着,看到一个小石子,哼,把小石子往河里一扔,河就泛起了层层波浪。这个时候就叫做冒泡
。
二、事件栗子(冒泡)
栗子
<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>
四、委托的局限性
- 比如 focus、blur 之类的事件本身没有事件冒泡机制,所以无法委托;
- mousemove、mouseout这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的;
五、使用委托的注意项(可以叫应用项)
- 只在必须的地方,使用事件委托,比如:ajax的局部刷新区域
- 尽量的减少绑定的层级,并且不在body元素上,进行绑定;(
事件委托的原理离不开DOM的查找;而浏览器太多层级的查找非常耗性能
) - 减少绑定的次数,如果可以,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行分发。
六、经典面试题
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');
};
讲解:
- over属于滑过(覆盖)事件,从父元素进入到子元素,属于离开了父元素,会触发父元素的out,触发子元素的over;enter属于进入,从父元素进入子元素,并不算离开父元素,不会触发父元素的leave,触发子元素的enter
- enter和leave阻止了事件的冒泡传播,而over和out还存在冒泡传播的
所以对于父元素嵌套子元素这种情况,使用OVER会发生很多不愿意操作的事情,此时我们使用ENTER会更加简单,操作方便,所以真实项目中ENTER的使用会比OVER多
此文章中未涉及到兼容性,有时间再加8⃣️