首页 > 代码库 > 事件捕获与事件冒泡

事件捕获与事件冒泡

一、背景

假设有一个HTML代码片段如下:

<div id="div">
    <input type="button" value="http://www.mamicode.com/点击测试"></input>
</div>

如果我们同时给 div 元素和 input 元素注册 click 事件,当点击 input 元素时,哪个事件先执行?

要回答这个问题,先得明白:

HTML文档是层级嵌套结构,页面元素处理事件时,总是最外层元素最先捕获到事件,再层层向下传递给子元素。这称为事件捕获阶段。

最里层子元素接收到事件后,再层层向上传递给父元素。这是事件的冒泡阶段。

两个阶段都可以处理我们感兴趣的事件,这就是下文介绍的事件注册模式。

二、事件注册模式

1、内联模式

<input type="button" value="点击测试" onlcick="alert(‘click on btn.‘)"></input>

这是最古老的事件注册模式,事件处理函数作为 HTML 元素属性被添加,由网景(Netscape)发明,除 IE3 for Mac 外所有浏览器都支持。不推荐使用。

2、传统模式

随着 DHTML 的出现,我们处理 web 页面的方式被彻底改变,事件注册模式必须变得灵活多样,以适应这种改变。于是浏览器厂商推出了新的事件注册模式。由于网景最先推出该模式,从而成为事实上的标准,后续包括 IE 在内的所有浏览器都支持该标准。写法如下:

// 添加事件处理操作
element.onclick = function(){ alert("click event!"); };

// 移除事件处理操作
element.onclick = null;

3、高级模式

为了解决传统模式的不足,微软推出了自己的事件注册模式,同时 W3C 也在 DOM2 规范中给出了注册模式。于是就有两种注册模式。

1)W3C模式

// 添加一个事件处理函数
element.addEventListener("click", function(){
    //xxx
}, false);

// 添加两个事件处理函数
element.addEventListener("click", doOne, false);
element.addEventListener("click", doTwo, false);

// 移除事件处理函数
element.removeEventListener("click", doOne, false);

W3C 模式接收 3 个参数,分别为事件类型、事件处理函数和事件处理阶段。说明如下:

  • 事件处理阶段参数,true=事件捕获,false=事件冒泡,你不确定的话,直接 false。
  • 事件处理函数中的 this 关键字即为元素自身。
  • 在 DOM3 规范中,新增了eventListenerList 属性,记录当前注册到该元素上的事件处理函数。
  • 即使使用 removeEventListener 方法移除一个未绑定的事件操作,也不会报错。

2)微软模式

// 添加一个事件操作
element.attachEvent("onclick", function(){
    //do something
});

// 添加两个事件操作
element.attachEvent("onclick", function(){
    // do something
});
element.attachEvent("onclick", handler);

// 移除事件操作
element.detach("onclick", handler);

不足:

》事件只能冒泡,不能捕获;

》事件处理函数是被引用,而不是复制,所以 this 关键字总是 window,完全没用。

以上两个不足导致的后果是,当事件冒泡时,我们无法知道当前是哪个元素在处理该事件。

三、回到问题

在 W3C 标准未出之前,网景采用“事件捕获”方式处理事件顺序,微软则采用“事件冒泡”方式。这里仅对 W3C 标准做说明。

W3C 标准兼容两种方式,将事件处理过程分作两个阶段:事件捕获阶段和事件冒泡阶段。如下图所示,注册在事件捕获阶段的事件处理函数在捕获阶段执行,注册在冒泡阶段的处理函数在冒泡阶段执行。

                       / \
----------| 捕 |------| 冒 |----------- | div | 获 | | 泡 | | | --------| 阶 |------| 阶 |-----------| | input | 段 | | 段 | |
| \ / | |--------------------------------------

当采用传统模式注册事件处理函数时,实际使用的是事件冒泡处理方式。

所以如果采用传统模式注册事件,则点击input元素时,先执行绑定在input上的click事件,再执行绑定在div上的click事件;

如果使用W3C标准,则根据第三个参数决定:true:捕获阶段处理,先执行div上的事件,再执行input上的事件;false则顺序相反。

在低版本 IE 浏览器下,只支持事件冒泡模式。

三、阻止事件传播

在使用传统模式和W3C标准时,事件处理函数默认接受一个事件对象参数。

如果不想让注册在父元素上的事件被子元素捕获,或者子元素的事件不想冒泡到父元素,则可以调用事件对象的 stopPropagation 方法。

比如 input 是 div 的子元素,所以当点击 input 时,会同时触发注册在 div 和 input 上的事件。

如果只想触发注册在 div 上的元素,则应在捕获阶段处理 div 上的事件,并在 div 的事件处理函数中调用 event 的 stopPropagation 方法,阻止事件向子元素传播。

如果希望点击 input 时只触发 input 的 click 事件,则应在冒泡阶段处理 div 上的事件,并在 input 的事件处理函数中调用 event 的 stopPropagation 方法,阻止事件冒泡到父元素。

 

参考链接:Early event handlers,Traditional event registration model,Advanced event registration models,Event order。

 

事件捕获与事件冒泡