昨晚和今天在复习JS事件流,那么,就来归纳总结JS事件流的一些知识点吧.首先,我们先来讲述下,当一个节点产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,路径通过的节点都会收到该事件,这个传播过程称为DOM事件流.
首先,我们先来解释下什么是事件:
Javascript和HTML之间的交互是通过事件实现的.事件,就是文档或浏览器窗口发生的一些特定的交互瞬间.可以使用监听器来预定事件,以便事件发生时执行相应的代码.
那么事件流又是什么呢?
事件流描述的就是从页面中接收事件的顺序,而早期的IE与Netscape提出了完全相反的事件流概念,IE事件流是事件冒泡,而NetScape的事件流就是事件捕获.
那么,接下来我们就介绍下事件冒泡与事件捕获.
IE提出的事件流是事件冒泡,即从下至上,从目标触发的元素逐级向上传播,直到window对象.
而Netscape的事件流就是事件事件捕获,即从document逐级向下传播到目标元素.
而DOM2事件的事件流则包含三个阶段:1).事件捕获阶段;2).处于目标阶段;3).事件冒泡阶段
接下来,我们来讲述下DOM2中的一些内容.
DOM2中定义了两个方法:
1.addEventListener() -- 添加事件侦听器
2.removeEventListener() -- 删除事件侦听器
函数均有三个参数,第一个参数是要处理的事件名,第二个参数是作为事件处理程序的函数,第三个参数是一个布尔值,默认为false表示使用冒泡机制,true表示捕获机制.
<div id="btn">点这个</div> <script> var btn=document.getElementById("btn"); btn.addEventListener("click",ljy,false); btn.addEventListener("click",ljy1,false); function ljy(){ console.log("ljy"); } function ljy1(){ console.log('ljy1') } </script>
这时候两个事件处理程序都可以成功触发,这说明了我们可以绑定多个事件处理程序,但是注意,如果使用了一模一样的监听方式的话,会发生覆盖的,即同样的事件和事件流机制下相同方法只会触发一次.
<div id="btn">点这个</div> <script> var btn=document.getElementById("btn"); btn.addEventListener("click",ljy,false); btn.addEventListener("click",ljy,false); function ljy(){ console.log("ljy"); } </script>
这时,ljy只会执行一次.而继续,我们把div扩展到3个.
<div id="btn3"> btn3 <div id="btn2"> btn2 <div id="btn1"> btn1 </div> </div> </div> <script> let btn1 = document.getElementById('btn1'); let btn2 = document.getElementById('btn2'); let btn3 = document.getElementById("btn3"); btn1.addEventListener("click",function{ console.log(1); },true) btn1.addEventListener("click",function{ console.log(2)},true) btn1.addEventListener("click",function{ console.log(3); },true) </script>
那么,这个执行顺序就是3->2->1了,最外层的btn最先触发,因为addEventListener最后一个参数是true,捕获阶段进行处理.
btn1.addEventListener('click',function(){ console.log('btn1捕获') }, true) btn1.addEventListener('click',function(){ console.log('btn1冒泡') }, false) btn2.addEventListener('click',function(){ console.log('btn2捕获') }, true) btn2.addEventListener('click',function(){ console.log('btn2冒泡') }, false) btn3.addEventListener('click',function(){ console.log('btn3捕获') }, true) btn3.addEventListener('click',function(){ console.log('btn3冒泡') }, false)
那么这个执行顺序就是,btn3捕获,btn2捕获,btn1捕获,btn1冒泡,btn2冒泡,btn3冒泡
我们看到先执行捕获阶段的处理程序,我们把顺序换一下再看看允许结果.
btn1.addEventListener('click',function(){ console.log('btn1冒泡') }, false) btn1.addEventListener('click',function(){ console.log('btn1捕获') }, true) btn2.addEventListener('click',function(){ console.log('btn2冒泡') }, false) btn2.addEventListener('click',function(){ console.log('btn2捕获') }, true) btn3.addEventListener('click',function(){ console.log('btn3冒泡') }, false) btn3.addEventListener('click',function(){ console.log('btn3捕获') }, true)
点击btn1的时候,这个执行顺序就是:
btn3捕获-->btn2捕获-->btn1冒泡-->btn1捕获-->btn2冒泡-->btn3冒泡
点击的是btn2的时候,这个执行顺序为:
btn3捕获-->btn2冒泡-->btn2捕获-->btn3冒泡
点击btn3的时候,这个执行顺序为:
btn3冒泡->btn3捕获
需要记住这个点:非目标元素捕获-目标元素代码顺序-非目标元素冒泡(最开始看的时候,还有点不太能明白)
阻止冒泡
有时候,我们需要点击事件不再继续向上冒泡,我们在btn2上加上stopPropagation函数,阻止程序冒泡.
btn1.addEventListener('click',function(){ console.log('btn1冒泡') }, false) btn1.addEventListener('click',function(){ console.log('btn1捕获') }, true) btn2.addEventListener('click',function(){ console.log('btn2冒泡') }, false) btn2.addEventListener('click',function(ev) { ev.stopPropagation(); console.log('btn2捕获') }, true) btn3.addEventListener('click',function(){ console.log('btn3冒泡') }, false) btn3.addEventListener('click',function(){ console.log('btn3捕获') }, true)
btn3捕获-->btn2捕获,可以看到btn2捕获阶段进行后不再继续往下执行.
接下来,我们顺带说下事件委托
如果有多个DOM节点需要监听事件的情况下,给每个DOM绑定监听函数,会极大的影响页面的性能,因此我们通过事件委托来进行优化,事件委托利用的就是冒泡原理.
<ul> <li>l</li> <li>j</li> <li>y</li> <li>1</li> <li>2</li> </ul> <script> var listen= document.getElementsByTagName('li') for(let index=0;index<listen.length;index++){ listen[index].addEventListener('click',function (ev){ console.log(ev.currentTarget.innerHTML) }) } </script>
按照上述代码,如果我们给每一个li都会绑定一个事件,但是如果这个时候li是动态渲染的,数据又特别大的时候,若是新增的时候,我们还得重新绑定,繁琐又耗能.这时我们可以将绑定事件委托到li的父级元素,ul上.
var ulee=document.getElementByTagName('ul') ulee[0].addEventListener('click',function(ev){ console.log(ev.target.innerHTML) })
上面的两种代码中,我们分别用了currentTarget与target,那么这两者的区别在哪里呢?
首先,target返回触发的元素,不一定是绑定事件的元素,而currentTarget返回的是绑定事件的元素
事件委托的出现,可以提高性能,减少占用的内存空间.并且即使新增加了元素也不需要杂糅化的添加.
还有个关于这方面看到的过的一道题,事件如何先捕获后冒泡呢?
在DOM标准事件模型中,是先捕获后冒泡的.但是如果要实现先冒泡后捕获的结果,对于同一个事件,监听捕获与冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件.
当然也有些不支持冒泡的事件:鼠标事件:mouseleave mouseenter 焦点事件:blur focus UI事件:scroll resize.