JS事件流(冒泡、捕获)addEventListener详解

前置知识:DOM结构
JS和HTML之间通过事件来进行交互。当文档或者浏览器窗口发生了特殊的交互,就是用监听器来预定事件,以便事件发生时执行响应的代码(观察者模式)。

事件流


随着浏览器的发展,浏览器开发团队遇到了一个问题:页面的哪一个部分会拥有某个特定的事件?举例来说,比如纸上有一组同心圆。把手指在同心圆上,那么手指指向的不止是一个圆,而是纸上的所有圆。所以后来浏览器开发团队认为,当你单击了某个按钮,除了单击了这个按钮,也单击了它的容器元素和整个页面。
事件流描述的是从页面中接收事件的顺序。
事件流有两种模式:分别是事件冒泡和事件捕获。

  • 以下图结构为例来解释事件冒泡和事件捕获
  • 布局图


    示例图.PNG
  • 当我们点击了line-two-center-child这个块元素时。DOM树当中发生的事件流顺序如下图。
    DOM事件流.PNG

事件捕获(event capture)


不太具体的node应该更早接收到事件,而最具体的node应该最后接收到事件。事件捕获的目的在于在事件到达预定目标之前捕获它。
在上图的布局中,绿色箭头代表的就是事件捕获发生时,DOM树中的node接收到事件的先后顺序。可以看到它是由最外层的document一层一层向内部传递事件。

事件冒泡(event bubble)


即为事件开始时从最具体的元素慢慢接收,然后逐级向上传播到较为不具体的节点。即为由最深的node向最外围的node扩散。
在上图布局中,红色箭头代表的就是事件捕获发生时,DOM树中的node接收到事件的先后顺序。可以看到它是由最内层的center-child一层一层向内部传递事件。

DOM事件流


“DOM标准事件流”:包含三个阶段事件捕获阶段、处于目标阶段、事件冒泡阶段。先后顺序是,事件捕获(可以截取事件)=>实际目标接收到事件=>冒泡阶段(可以对这个事件作出响应)

在捕获阶段,捕获不会到达具体的目标节点,到达目标节点处理事件被看作是冒泡阶段的一部分。

DOM0级事件

直接指定每个元素上面的事件处理属性。DOM0级方法指定的事件处理方法认为是元素的方法。因此,这个时候事件处理程序是在元素的作用域中运行的。this指向引用当前元素。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
   console.log(this.id);//myBtn
}

DOM0级事件会在事件流的冒泡阶段被处理。
删除0级事件btn.onclick = null

DOM2级事件

addEventListener('eventName',callback,ifbubble):三个参数分别是事件名字符串,回调函数,布尔值。第三个参数为true,表示在捕获阶段调用事件处理程序。为false,表示在冒泡阶段调用事件处理。默认是使用冒泡。
removeEventListener():移除addEventListener添加的事件

2级事件和0级事件的区别:2级事件可以添加多个事件处理程序,处理顺序会按照它们的添加顺序触发,而0级事件只有最后一次添加能生效。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
   console.log(1);
}
btn.onclick = function(){
   console.log(2);
}
//btn点击后输出2
var btn = document.getElementById("myBtn");
btn.addEventListener('click', function(){
   console.log(1);
},false)
btn.addEventListener('click', function(){
   console.log(2);
},false)
// btn点击后先输出1再输出2
  • 2级事件的问题:如果addEventListener()添加的回调是匿名函数,会无法通过removeEventListener()移除。
btn.addEventListener('click', function(){
   console.log(2);
},false)
btn.removeEventListener('click', function(){
   console.log(2);
},false);//无法删除

因为传入的匿名函数会认为是一个全新的函数。

事件对象对象(event)

在触发DOM上的某个事件的时候,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。

btn.addEventListener('click', function(event){
   console.log(event.type);//"click"
},false);


event对象的主要成员

属性方法 类型 说明
currentTarget Element 其事件处理程序当前正在处理的那个元素,比如,因为子node元素的方法触发了,冒泡到父node的的时候,currentTarget指向父node
target Element 事件的目标节点,也就是target一般是不变的,在哪个node触发就是哪个node
type String 被触发的事件类型
bubbles Boolean 表明事件是否冒泡
cancelable Boolean 表明是否可以取消事件的默认行文
defaultPrevented Boolean 为true表明已经调用了preventDefault()
eventPhase Interger 调用事件处理的阶段,1为捕获,2为处于目标,3为冒泡
preventDefault() Function 取消事件的默认行为
stopPropagation() Function 取消事件的进一步捕获或冒泡

在事件回调方法内部,this始终等于currentTarget。只有在事件处理程序执行期间,event对象才会存在;一旦事件狐狸程序执行完成,event对象就会被销毁。

事件委托


在js当中,添加到页面上的eventhandler的数量会直接关系到页面的整体运行性能。因为,每个函数都是对象,会占用内存,内存中对象越多性能就越差,其次,必须事先指定所有eventhandler而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
对于eventhandler过多的问题解决方案就是事件委托

事件委托:利用了事件冒泡,只指定一个eventhandler就可以管理某一类型的所有事件。

如果想要给3个li添加事件监听,从事件委托的角度来看,只需要给父元素ul添加监听器,那么3格li上的事件触发的时候,都会冒泡到ul。通过检测target对象(target就是触发事件的最底层对象)上的id可以判断具体是哪个li触发的操作。

移除EventHandler

当元素绑定eventhandler时,运行中的浏览器代码与js之间会建立一个连接。连接越多,页面执行越慢。

  • 当通过纯粹的DOM操作,如removeChild()replaceChild(),或者在使用innerHTML替换页面中某一部分,绑定了eventhandler的元素被innerHTML删除了,会导致原来添加到元素中的eventhandler很有可能不被当作垃圾回收。
  • 在卸载页面的时候可能存在空事件处理程序。
尽量使用事件委托可以减少事件绑定。

你可能感兴趣的:(JS事件流(冒泡、捕获)addEventListener详解)