事件冒泡 和 事件捕获

背景知识

什么是事件?
直观的说就是网页上发生的事情,大部分是指用户的鼠标动作和键盘动作,如点击、移动鼠标、按下某个键。为什么说大部分呢,因为事件不单单只有这两部分,还有其他的例如document的load和unloaded。只不过我们更加关注的是用户的操作。

事件模型:规范事件的定义的一种标准

事件的三种模型:

  • 原始事件模型(DOM 0级事件模型)
  • IE事件模型
  • DOM2事件模型
DOM2事件模型:

此模型是W3C制定的标准模型,既然是标准,那大家都得按这个来,我们现在使用的现代浏览器(指IE6~8除外的浏览器)都已经遵循这个规范。W3C制定的事件模型中,“DOM2级事件”中规定的事件流同时支持了事件捕获阶段和事件冒泡阶段,而作为开发者,我们可以选择事件处理函数在哪一个阶段被调用。
一次事件的发生包含三个过程:

(1)capturing phase:事件捕获阶段。
事件被从document一直向下传播到目标元素,在这过程中依次检查经过的节点是否注册了该事件的监听函数,若有则执行。

(2)target phase:事件处理阶段。
事件到达目标元素,执行目标元素的事件处理函数.

(3)bubbling phase:事件冒泡阶段。
事件从目标元素上升一直到达document,同样依次检查经过的节点是否注册了该事件的监听函数,有则执行。

所有的事件类型都会经历captruing phase(事件捕获),但是只有部分事件会经历bubbling phase(事件冒泡)阶段,例如submit事件就不会被冒泡。

1. 是什么?

<div id="outer">
    <p id="inner">Click me!</p>
</div>

上面的代码当中一个div元素当中有一个p子元素,如果两个元素都有一个click的处理函数,那么我们怎么才能知道哪一个函数会首先被触发呢?

为了解决这个问题微软和网景提出了两种几乎完全相反的概念。

**事件流 : ** 指的是页面中接收事件的顺序.

事件冒泡

微软提出了名为事件冒泡(event bubbling)的事件流。
事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。

当一个DOM元素上的事件被触发的时候(如:按钮点击事件),这个元素的所有父元素 中,如果也绑定有该相同事件,则也会被触发, 触发的顺序就是先从 : 当前元素的事件 ==> 临近父元素 ==> 父元素......,这一过程被称为事件冒泡

因此上面的例子在事件冒泡的概念下发生click事件的顺序应该是:
p -> div -> body -> html -> document
IE,火狐和chrome浏览器都是事件冒泡.

事件捕获

网景提出另一种事件流名为事件捕获(event capturing)。
当一个DOM元素上的事件被触发的时候(如:按钮点击事件),这个元素的所有父元素 中,如果也绑定有该相同事件,则也会被触发, 触发的顺序就是先从 : ....... 父元素 ==> 临近父元素 ==> 当前元素的事件,这一过程被称为事件捕获
与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
上面的例子在事件捕获的概念下发生click事件的顺序应该是:
document -> html -> body -> div -> p

图解:


事件冒泡和捕获具体如图

2. 解决了什么问题?

这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。

http://www.imooc.com/article/9833

3. 执行原理

<ul>
   <li>
       <p>
          <a>  </div>
      </p>
   </li>
</ul>
事件冒泡和事件捕获原型图

事件捕获阶段:事件从最上一级标签开始往下查找,直到捕获到事件目标(target)。
事件冒泡阶段:事件从事件目标(target)开始,往上冒泡直到页面的最上一级标签。

事件捕获当你使用事件捕获时,父级元素先触发,子级元素后触发,即div先触发,p后触发。
事件冒泡当你使用事件冒泡时,子级元素先触发,父级元素后触发,即p先触发,div后触发。

4. 如何使用?

说到事件的执行顺序,那么我们需要知道一个给元素添加事件的方法, 即给某元素动态绑定事件
W3C为我们提供了addEventListener()函数用来为指定的dom元素动态绑定事件。

语法:
element.addEventListener( event ,  function ,  useCapture )

参数分析

提示: 使用 removeEventListener()方法来移除 addEventListener() 方法添加的事件句柄。

function sayHello() { 
console.log("hello");
}
var myDiv = document.getElementById("myDiv");
myDiv.addEventListener("click", sayHello);

这样我们点击id为myDiv的元素时,控制台就会输出"Hello"。
事件冒泡

有如下html代码:


html代码
运行结果

下面设置了四个函数用来进行事件绑定:


要绑定的函数

使用下面的代码,我们可以获取四个元素对应DOM


四个DOM元素

现在,我试着同时分别为grandpa和grandson绑定sleep和doingHomework事件:

绑定事件

这时我们点击最外层的grandpa时,当然会触发sleep函数,然而当我们点击grandson时,控制台的输出如下:

控制台查看结果

原因:这是因为grandson在grandpa之上,当点击grandson时,同时也在grandpa上进行了点击操作,所以在执行了doingHomework后,还会触发grandpa的sleep函数。
这种当满足条件后从子元素到父元素依次触发其上事件的处理方式叫做事件冒泡

我们也为father和child分别绑定watchTV和playingCard函数

绑定事件
控制台查看结果
事件冒泡()
grandpa.addEventListener("click", sleep);            
grandson.addEventListener("click", doingHomework);            
father.addEventListener("click", watchTV);            
child.addEventListener("click", playingCard);
四个元素都绑定相同事件,点击grandson时的结果
点击child元素的 结果

事件捕获

事件捕获与事件冒泡完全相反,先触发祖先元素的事件,然后再逐级触发子元素的事件。默认情况下,绑定事件时,采用事件冒泡原则,如果想要进行事件捕获的话,需要设置一个参数 。

可以为addEventListener函数添加第三个参数useCapture,参数值是布尔值,默认是false。当useCapture为false时,事件处理采取事件冒泡的原则,当userCapture为true时,则采取事件捕获的原则

绑定捕获事件

这时,当点击grandson时,就会先执行祖先元素的事件,再执行后代元素的事件了,控制台的输出如下图所示:

捕获事件的执行结果

虽然默认情况下,useCapture的值是false,但我推荐我们在绑定函数时把它明显的写出来以避免浏览器兼容性的问题。

事件冒泡与事件捕获要是同时进行怎么办

有思想的同学肯定会思考这样一个问题,在上述绑定事件的代码中,第三个参数不是全部设置的true,就是全部设置成false,那如果既有true,又有false,有的元素设置成按事件冒泡处理,有的元素设置成按事件捕获处理,那怎么办呢?
直接告诉大家答案,我们的浏览器更“喜爱”事件捕获:

它会先把useCapture为false的元素绑定事件放到一边,按照事件捕获正常的顺序执行useCapture为true的元素绑定事件,最后在按照事件冒泡顺序执行useCapture为false。
现在我们作如下更改

既有事件冒泡又有事件捕获

按照上述原则,当点击grandson时,先执行useCapture为true的元素的绑定事件,又按照事件捕获原则,先执行grandpa的事件,再执行child的事件。之后,再按照事件捕获顺序执行useCapture为false的事件,输出结果如下:
细说addEventListener与事件捕获、事件冒泡_

阻止事件冒泡和捕获

我们可以利用时间对象event的stopPropagation()方法阻止事件的进一步传播。
我们修改一下doingHomework函数:

为grandson的事件设置阻止事件对象
设置阻止事件后,点击grandson的结果

发现事件执行到doingHomework就被阻断了,其后不会在事件传播到父元素。
值得注意的是,event.stopPropagation()函数并不会阻止其下函数内容的执行。

*如果你使用的是jquery的事件绑定,也可以直接在函数中使用return false来阻止事件的传播(当然event.stopPropagation同样有效),与event.stopPropagation不同的是,return false会阻止同函数下面的代码执行 *

传统绑定事件方式在一个支持W3C DOM的浏览器中,像这样一般的绑定事件方式,是采用的事件冒泡方式。
ele.onclick = doSomething2

IE浏览器
如上面所说,IE只支持事件冒泡,不支持事件捕获,它也不支持addEventListener函数,不会用第三个参数来表示是冒泡还是捕获,它提供了另一个函数attachEvent。
ele.attachEvent("onclick", doSomething2);

不是所有的事件都能冒泡,例如:blur、focus、load、unload。

事件冒泡的好处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容