事件传播(事件捕获与事件冒泡)

 EventTarget接口

  DOM 的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequestAudioNodeAudioContext)也部署了这个接口。

该接口主要提供三个实例方法。

  • addEventListener:绑定事件的监听函数
  • removeEventListener:移除事件的监听函数
  • dispatchEvent:触发事件

 

事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。

事件冒泡

微软提出了名为事件冒泡(event bubbling)的事件流。事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。

因此在事件冒泡的概念下在p元素上发生click事件的顺序应该是

p -> div -> body -> html -> document

 

事件捕获

网景提出另一种事件流名为事件捕获(event capturing)。与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。

因此在事件捕获的概念下在p元素上发生click事件的顺序应该是

document -> html -> body -> div -> p

 

在微软和网景打得火热之时,w3c 采用折中的方式,制定了统一的标准——先捕获再冒泡

addEventListener的第三个参数就是为冒泡和捕获准备的.

addEventListener有三个参数:

element.addEventListener(event, function, useCapture)

第一个参数是需要绑定的事件
第二个参数是触发事件后要执行的函数
第三个参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。

 

注意,第三个参数useCapture除了布尔值,还可以是一个属性配置对象。该对象有以下属性。

 

  • capture:布尔值,表示该事件是否在捕获阶段触发监听函数。
  • once:布尔值,表示监听函数是否只触发一次,然后就自动移除。
  • passive:布尔值,表示监听函数不会调用事件的preventDefault方法。如果监听函数调用了,浏览器将忽略这个要求,并在监控台输出一行警告。

 

 

事件捕获vs事件冒泡

 

当事件捕获和事件冒泡一起存在的情况,事件又是如何触发呢。
这里把被点击的DOM节点为target节点

  1. document 往 target节点,捕获前进,遇到注册的捕获事件立即触发执行

  2. 到达target节点,触发事件(对于target节点上,是先捕获还是先冒泡则按照捕获事件和冒泡事件的注册顺序,先注册先执行)

  3. target节点 往 document 方向,冒泡前进,遇到注册的冒泡事件立即触发

总结下就是:

    • 对于非target节点则先执行捕获在执行冒泡

    • 对于target节点则是先执行先注册的事件,无论冒泡还是捕获

冒泡还是捕获?

对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说还是建议大家使用事件冒泡模型。

 

IE浏览器兼容

IE浏览器对addEventListener兼容性并不算太好,只有IE9以上可以使用。

要兼容旧版本的IE浏览器,可以使用IE的attachEvent函数

object.attachEvent(event, function)

两个参数与addEventListener相似,分别是事件和处理函数,默认是事件冒泡阶段调用处理函数,要注意的是,写事件名时候要加上"on"前缀("onload"、"onclick"等)。

 

封装函数

 1        //添加事件
 2             function addEvent(obj,type,fn){
 3                 if(obj.addEventListener){
 4                     obj.addEventListener(type,fn,false);
 5                 }else{
 6                     obj.attachEvent("on"+type,fn);
 7                 }
 8             }
 9             //移除事件
10             function romeveEvent(obj,type,fn){
11                 if(obj.removeEventListener){
12                     obj.removeEventListener(type,fn)
13                 }else{
14                     obj.detachEvent("on"+type,fn);
15                 }
16             }

事件冒泡应用:事件代理(事件委托)

  由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

在实际的开发当中,利用事件流的特性,我们可以使用一种叫做事件代理(事件委托)的方法。

  使用事件代理的好处不仅在于将多个事件处理函数减为一个,而且对于不同的元素可以有不同的处理方法。

var oUl=document.querySelector("ul");
oUl.onclick=function(e){
  var evt = e || event;
  if(evt.nodeName.toLowerCase=="li"){
    alert("li");  
  }       
}

 接口target属性是对Event调度事件的对象的引用。它与Event.currentTarget在事件的冒泡或捕获阶段调用事件处理程序时不同

event.target可以使用属性来实现事件代理

在IE 6-8上,事件模型是不同的。事件侦听器使用非标准EventTarget.attachEvent()方法附加在此模型中,事件对象具有Event.srcElement属性而不是target属性,并且它具有与之相同的语义Event.target

function hide(e) {
  // 兼容处理
  var evt=e || event;
  var target = evt.target || evt.srcElement;
  target.style.visibility = 'hidden';
}

 

 

如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法(IE为cancelBubble)。

if(evt.stopPropagation){
    evt.stopPropagation();
}else{
    evt.cancelBubble = true;
}        

 

 

但是,stopPropagation方法只会阻止事件的传播,不会阻止该事件触发<p>节点的其他click事件的监听函数。也就是说,不是彻底取消click事件。

p.addEventListener('click', function (event) {
  event.stopPropagation();
 //会触发 console.log(
1); }); p.addEventListener('click', function(event) { // 会触发 console.log(2); });

 

上面代码中,p元素绑定了两个click事件的监听函数。stopPropagation方法只能阻止这个事件向其他元素传播。因此,第二个监听函数会触发。输出结果会先是1,然后是2。

如果想要彻底阻止这个事件的传播,不再触发后面所有click的监听函数,可以使用stopImmediatePropagation方法。

p.addEventListener('click', function (event) {
  event.stopImmediatePropagation();
  // 会触发
  console.log(1);
});

p.addEventListener('click', function(event) {
  // 不会被触发
  console.log(2);
});

上面代码中,stopImmediatePropagation方法可以彻底阻止这个事件传播,使得后面绑定的所有click监听函数都不再触发。所以,只会输出1,不会输出2。

阮一峰老师网站上有着详尽的解释,如有需求,敬请关注

 

posted @ 2019-03-07 21:37  gitByLegend  阅读(969)  评论(0编辑  收藏  举报