`
marlonyao
  • 浏览: 252710 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Capture和Bubble事件阶段

阅读更多
做过Web开发应该都知道addEventListener,它接受三个参数,前两个都很好懂,分别表示事件类型和事件处理器,而最后一个参数是useCapture,就有点让费解了,它是boolean参数,为true时表示Capture阶段触发事件,为false表示在Bubble阶段触发事件。之前一直没有怎么关注这个参数,对它只是一知半解,主要原因在于IE使用AttachEvent来注册事件处理器,但它不支持useCapture,一律表示在冒泡阶段触发事件,所以为了兼容IE这位老大哥,addEventListener的useCapture通常设置为false。如果使用更高级的类库,如jQuery,那么它已经帮你做好这些了。但我最近在做Firefox下的插件开发,我知道Firefox是支持useCapture参数的,看着有些地方使用false,有些地方使用true,而我又不知道它们这么用的原因,这总令人不痛快,因此这才花时间将它弄清楚,也正是这篇博客所要讲的。

要搞清楚为什么要有Capture和Bubble两个阶段,实际上加上触发事件的元素(target)一共有三个阶段,首先得搞清楚事件的传播。事件需要传播的原因在于事件的实际触发者很可能没有注册事件处理器,而只在它的父元素上注册了同一事件的处理器,这时事件就要传播到父元素。还是举例来得明白,看下面的HTML文档:
<html>
<body>
	<p id="the_p">This is a <a id="the_a" href="#"><span id="the_span">SAMPLE</span></a> text.</p>
</body>
<script>
	function clickHandler(event) {
		var eventInfo = "event target: " + event.target.tagName +
				", current target: " + event.currentTarget.tagName;
		alert(eventInfo);
	}
	document.getElementById('the_a').addEventListener('click', clickHandler, false);
	document.getElementById('the_p').addEventListener('click', clickHandler, false);
</script>
</html>

在上面的例子中,我们在A和P元素上分别注册了同一个事件处理器,但并没有在SPAN元素上注册,当我们点击“SAMPLE”时,实际点击的SPAN元素,那么是不是因为SPAN上没有事件处理器就忽略这个事件了呢?显然不是,因为我们在A和P上注册了处理器,对A和P来说,点击SPAN相当于也点击了A和P,当然也点击了BODY和DOCUMENT,所以也要触发A和P上的事件处理器,也就是说事件需要从SPAN传播到A到P,然后到BODY和DOCUMENT,每个被传播到的元素都有机会处理这个事件(注意:这里描述的传播顺序不准备,下面还会讲到)。

每个事件处理器都接受一个event参数,它有相当多的属性,随着事件类型而定,但常见的也就那么几个,上面的例子使用了两个很重要的属性,target表示事件的实际触发者,在这个例子中就是SPAN,currentTarget表示正在处理事件的元素,如果是A正在处理这个事件,那么event.currentTarget就是A元素,如果P元素正在处理事件,那么event.currentTarget就是P元素。也就是说,对同一个事件,event.target总是事件的触发者,而event.currentTarget视谁正在处理这个事件而定,event.currentTarget总是等于this(在JavaScript中使用this总需要格外小心,因为JavaScript可以通过call或apply改变this)。


另外一个问题,既然A和P的事件处理器都会触发,那么那个会先触发呢?这就要涉及到事件传播的三个阶段,它们分别是捕获(Capture)阶段,事件目标(Target)阶段和冒泡(Bubble)阶段。它们的顺序是Capture阶段最先,Target居中,Bubble阶段最后。文字描述太抽像,依然拿上面的例子来说,当我们点击SPAN元素之后,click事件的传播会分成三个阶段:
	Capture阶段:DOCUMENT -> BODY -> P -> A
	Target阶段:SPAN
	Bubble阶段:A -> P -> BODY -> DOCUMENT

我们可以看到Catpure阶段和Bubble阶段经历的元素顺序刚好相反。对每个事件,除了target元素以外,每个元素会被传播两次,但是其事件处理器只能触发一次,到底在哪一次触发就取决于useCapture参数了,如果useCapture为true,表示在第一次传播到该元素时就触发(即Capture阶段),否则表示第二次传播到该元素才触发(即Bubble阶段),对于target元素,由于它只会传播一次,所以指定useCapture没有意义(但必须指定,因为该参数是必须的),它总是在传播的中间点触发(即Target阶段)。对上面的例子,由于A和P调用addEventListener时useCapture均为false,所以它们都只在Bubble阶段触发。这样,在Capture阶段不会触发任何事件处理器,因为SPAN上没有注册事件处理器,所以Target阶段也不会触发事件处理器,在Bubble阶段,会在A和P上触发事件处理器,但由于事件先传播到A然后再传播到P,所以A的事件处理器先于P的事件处理器被触发。这可以从运行的結果看出来,先弹出的警示框显示current target为A,然后才弹出current target为P的警示框。


现在将注册事件的过程改一下,变成都在Capture阶段触发,如下:
	document.getElementById('the_a').addEventListener('click', clickHandler, true);
	document.getElementById('the_p').addEventListener('click', clickHandler, true);

这样的结果会怎样呢?分析下事件传播过程,很快就能得到结论,两个事件处理器都在Capture阶段触发,而在Capture阶段,事件先传播到P然后再传播到A,所以P的事件处理器先于A的事件处理器被触发。改成下面的又会怎样?
	document.getElementById('the_a').addEventListener('click', clickHandler, false);
	document.getElementById('the_p').addEventListener('click', clickHandler, true);

也就是A的事件处理器在Bubble阶段触发,P的在Capture阶段触发,分析事件传播过程,由于Capture阶段总是先于Bubble阶段,所以P的事件处理器会先触发。

现在我们可以看到事件传播所经过的元素形成了一个链条,对上面的例子,这个链条即是DOCUMENT -> BODY -> P -> A -> SPAN -> A -> P -> BODY -> DOCUMENT。那么这个传播链条是如何确定的呢?其实很简单,想必读者也能够猜想得到,在事件触发之时,首先确定target元素(这里是SPAN),然后确定它的父元素(A),再找父元素的父元素(P),这样一直下去,直到找文档根元素(DOCUMENT),这相当于Bubble阶段的传播过程,得到事件的整个传播过程,只需要将这个链条反过来并去掉target元素,添加原来链条的开头就得到事件的整个传播路径了。整个路径是在事件触发之时就确定了,即使在事件传播过程删除了路径中的一些元素,注册在其上的事件处理器也依然会触发。看例子:
	function pHandler(event) {
		// remove A
		document.getElementById("the_p").removeChild(document.getElementById("the_a"));
		var eventInfo = "event target: " + event.target.tagName +
				", current target: " + event.currentTarget.tagName;
		alert(eventInfo);
	}
	function aHandler(event) {
		var eventInfo = "event target: " + event.target.tagName +
				", current target: " + event.currentTarget.tagName;
		alert(eventInfo);
	}
	document.getElementById('the_p').addEventListener('click', pHandler, true);
	document.getElementById('the_a').addEventListener('click', aHandler, false);

P事件处理器在Capture阶段触发而A的在Bubble阶段触发,因此P会先触发,它的事件处理器中会先删除A元素,然后显示事件信息。尽管删除了A,它注册其上的事件处理器还是会触发,因为它的删除是在传播过程中删除的,而事件的传播路径是在事件触发之时就已经确定了。

默认情况下事件传播会走完完整的三个阶段,但event的stopPropagation()可以中止事件的传播。
	function clickHandler(event) {
		var eventInfo = "event target: " + event.target.tagName +
				", current target: " + event.currentTarget.tagName;
		alert(eventInfo);
		event.stopPropagation();
	}

	document.getElementById('the_p').addEventListener('click', clickHandler, true);
	document.getElementById('the_a').addEventListener('click', clickHandler, false);

对上面的例子,会先在Capture阶段触发P的事件处理器,它调用了stopPropagation()方法来阻止事件的继续传播,因此A元素便接收不到事件了。IE的event的cancelBubble属性,为true时会阻止事件向上Bubble。jQuery中的event.stopPropagation()方法对IE浏览器就是通过设置event的cancelBubble属性来实现阻止事件的传播的。但是两者从语义上来说是有区别的,stopPropagation()会阻止任意阶段的事件传播,而cancelBubble只阻止Bubble阶段的事件传播。但由于IE的限制,jQuery之类的高层类库只使用Bubble方式的事件传播,这样stopPropagation()就是cancelBubble(因为没有Capture),两者的功能就是一样的了。

说了这么多,想必读者应该清楚Capture和Bubble这两个事件传播阶段了吧,至于什么时候该用Capture,什么时候该用Bubble,还请读者自己裁决。大致的原则,如果你想事件尽快地执行,那就使用Capture,否则便使用Bubble。但是,不要忘记了一个前提,IE只支持Bubble传播方式,如果你的应用需要支持IE,那么事情就简单多了,那就是永远将useCapture设为false。
分享到:
评论
1 楼 fengzifz 2013-09-25  
太好了,我终于搞明白bubble事件了

相关推荐

    深入理解事件冒泡(Bubble)和事件捕捉(capture)

    事件冒泡(Bubble)和事件捕捉(Capture)是事件处理的两种不同机制,它们定义了事件如何从DOM树的深层元素传播到顶层元素。深入理解这两者有助于优化事件处理的性能和功能。 首先,事件的发生顺序是一个关键概念。...

    Android-传统事件捕获和冒泡的流程解析

    这个过程可以分为两个阶段:事件捕获(Capture)和事件冒泡(Bubble)。 1. **事件捕获阶段**: - **事件传递开始**:事件首先由最顶级的ViewGroup接收到,通常是DecorView。 - **向上传递**:事件按照从底部到...

    JS事件冒泡浏览器兼容

    在JavaScript中,有多种事件模型,包括事件冒泡(Bubble Phase)和捕获阶段(Capture Phase)。事件冒泡是最常见的一种,它始于事件目标元素(例如,用户点击的按钮),然后依次经过其所有父元素,直到到达文档的根...

    Flex4视频教程_02-01事件概述.rar

    3. **事件生命周期**:事件经历三个阶段:捕获阶段(Capture Phase)、目标阶段(Target Phase)和冒泡阶段(Bubble Phase)。在捕获阶段,事件从最顶层的根节点向目标节点传递;在目标阶段,事件到达触发事件的对象...

    flex 事件学习

    事件流分为三个阶段:目标阶段(Target Phase)、捕获阶段(Capture Phase)和冒泡阶段(Bubble Phase)。捕获阶段从根节点开始,向目标节点移动,目标阶段到达实际事件目标,最后冒泡阶段从目标节点向根节点回溯。 ...

    可以解析每个按钮的点击事件所上报的数据

    在OpenHarmony中,事件处理通常基于事件传递模型,例如事件流模型(Event Flow Model),其中包括冒泡(Bubble)和捕获(Capture)两个阶段。开发者可以选择在哪个阶段处理事件,或者同时在两个阶段处理。对于按钮...

    js 动态给元素添加、移除事件的实现方法

    capture/bubble参数是一个布尔值,用来指定事件处理是在捕获阶段还是冒泡阶段进行。 2. IE浏览器的兼容性问题: 文章提到了在IE浏览器中不支持addEventListener和removeEventListener方法,而使用attachEvent和...

    flexevent.rar_flex

    2. 捕获(Capture):如果事件冒泡,它会沿着组件层次结构从根节点向事件源捕获。捕获阶段不是所有事件都有的,只有那些在事件冒泡路径上设置了捕获监听器的事件才会经历这个阶段。 3. 目标(Target):事件到达...

    JavaScript实现父子dom同时绑定两个点击事件,一个用捕获,一个用冒泡时执行顺序的方法

    当页面上有父子关系的DOM元素分别绑定了多个事件处理器,且这些事件处理器分别在捕获阶段和冒泡阶段时,其执行顺序可能会让人困惑。因此,理解DOM事件的传播机制以及如何控制事件监听的执行顺序对于进行有效和正确的...

    Flex 3 基础教程(3)

    1. 捕获阶段(Capture Phase):事件从最顶层的根节点开始,逐层向下传播到目标节点,如果沿途的节点注册了捕获事件监听器,相应的监听器会被调用。 2. 目标阶段(Target Phase):事件到达目标节点,执行该节点上...

    javascript下对于事件、事件流、事件触发的顺序随便说说

    `addEventListener`方法是DOM中用于添加事件监听器的标准方法,接受三个参数:事件类型、处理函数和布尔值,该布尔值决定事件是捕获还是冒泡阶段触发。当第三个参数为`true`时,事件将在捕获阶段处理;为`false`,则...

    ohos-extension-master.zip

    触摸事件的分发包括了三个主要步骤:捕捉阶段(Capture)、目标阶段(Target)和冒泡阶段(Bubble)。在鸿蒙的扩展中,开发者可以更加灵活地控制事件的传递路径,比如自定义事件拦截、处理策略,以满足复杂交互需求...

    bigevent:122个学习大事件

    2. **事件监听器**:讲解如何使用`addEventListener`和`removeEventListener`方法来添加和移除事件监听器,以及如何处理捕获(capture)和冒泡(bubble)阶段。 3. **事件处理器**:解释如何编写事件处理器函数,...

    图形用户界面

    3. **混合模型(Mixed Model)**:结合冒泡和捕获,允许事件在冒泡和捕获阶段都被处理。 了解和掌握这些基本概念,以及事件处理机制,将有助于我们更好地设计和实现GUI应用程序,提供用户友好的交互体验。在实际...

    编写高性能的javascrip一些资料

    - 避免使用`addEventListener`的`capture`阶段,因为它会比`bubble`阶段早触发,可能会导致性能下降。 4. **异步编程**: - AJAX是异步数据交换的关键,理解Promise、async/await和回调函数的用法,可以有效管理...

    浅谈JavaScript的事件

    现代浏览器普遍支持两种模式,通常事件默认从捕获阶段开始,然后进入冒泡阶段。 2. **事件冒泡(Event Bubbling)** 如代码示例所示,当用户点击`点击我&lt;/div&gt;`时,事件会按照`div -&gt; body -&gt; html -&gt; document`的...

    javascript相关事件的几个概念

    - **冒泡阶段(bubble phase)**:事件从目标元素向上冒泡到最外层的父元素,允许沿途的父元素有机会处理事件。 注册事件处理程序有多种方式: - **通过设置JavaScript对象属性**:如`element.onclick = function...

    js中的事件捕捉模型与冒泡模型实例分析

    在JavaScript中,事件处理主要有两种模型:事件捕捉(Capture)模型和冒泡(Bubble)模型。这两个模型定义了事件如何从最深的元素(在HTML结构中)向上传播到最外层的元素。 事件捕捉模型是事件处理的第一阶段,它...

Global site tag (gtag.js) - Google Analytics