Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

理解事件冒泡和事件捕获 #4

Open
xunan007 opened this issue Feb 23, 2018 · 0 comments
Open

理解事件冒泡和事件捕获 #4

xunan007 opened this issue Feb 23, 2018 · 0 comments

Comments

@xunan007
Copy link
Owner

xunan007 commented Feb 23, 2018

最近在复习前端的基础,看到事件这一节的时候,刚好发现了笔记中一道特别好玩并且十分有趣的代码,根据这么一道题目,基本上能够把事件冒泡和事件捕获的盲区给扫空。本文就带你一起来看看这段有趣的代码。

但是,首先我们还是要例行公事,把一些基础的概念过一过,其实也不需要太长的时间。

什么是事件?

JavaScriptHTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口发生的一些特定的交互瞬间。可以使用监听器(或事件处理程序)来预定事件,以便事件发生时执行相应的代码。通俗的说,这种模型其实就是一个观察者模式。(事件是对象主题,而这一个个的监听器就是一个个观察者)

事件流

事件流描述的就是从页面中接收事件的顺序。而IENetscape提出了完全相反的事件流概念。IE事件流是事件冒泡,而Netscape的事件流就是事件捕获。

打个比喻,如果你把手指放在圆心上(多个同心圆),那么你的手指指向的不是一个圆,而是纸上所有的圆。如果你单击了某个按钮,在单击按钮的同时,也单击了按钮的容器元素,甚至是整个页面。

事件冒泡

IE的事件流叫做事件冒泡。即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。所有现代浏览器都支持事件冒泡,并且会将事件一直冒泡到window对象。

事件捕获

事件捕获的思想是不太具体的节点应该更早的接收到事件,而在最具体的节点应该最后接收到事件。事件捕获的用以在于事件到达预定目标之前捕获它。IE9+、Safari、Chrome、OperaFirefox支持,且从window开始捕获(尽管DOM2级事件规范要求从document)。由于老版本浏览器不支持,所以很少有人使用事件捕获。

DOM事件流

“DOM2级事件”规定事件流包括三个阶段,事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的事件捕获,为截获事件提供了机会。然后是实际的目标接收了事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。

以上这段话,就是我们DOM事件流的根本了,这段话非常重要,虽然看似简单,但是里面包含着很多小细节,我会在下面例子中全部讲到。

一个十分有趣的例子

现在就开始来讲解这个有趣的例子,先把代码贴上来,大致的结构十分简单。由于segmentfault代码过长时会有滚动条,这里我把它切分,并省略了一些不必要的结点,便于观看。

<div id="a">
    <div id="b">
        <div id="c"></div>
    </div>
</div>
#a{
	width: 300px;
	height: 300px;
	background: pink;
}
#b{
	width: 200px;
	height: 200px;
	background: blue;
}
#c{
	width: 100px;
	height: 100px;
	background: yellow;
}
var a = document.getElementById("a"),
    b = document.getElementById("b"),
    c = document.getElementById("c");
c.addEventListener("click", function (event) {
    console.log("c1");
    // 注意第三个参数没有传进 false , 因为默认传进来的是 false
    //,代表冒泡阶段调用,个人认为处于目标阶段也会调用的
});
c.addEventListener("click", function (event) {
    console.log("c2");
}, true);
b.addEventListener("click", function (event) {
    console.log("b");
}, true);
a.addEventListener("click", function (event) {
    console.log("a1");
}, true);
a.addEventListener("click", function (event) {
    console.log("a2")
});
a.addEventListener("click", function (event) {
    console.log("a3");
    event.stopImmediatePropagation();
}, true);
a.addEventListener("click", function (event) {
    console.log("a4");
}, true);

整个的html页面就是下面这三个小盒子。

那么现在有三个问题:

  • 如果点击c或者b,输出什么?(答案是a1、a3

    stopImmediatePropagation包含了stopPropagation的功能,即阻止事件传播(捕获或冒泡),但同时也阻止该元素上后来绑定的事件处理程序被调用,所以不输出 a4。因为事件捕获被拦截了,自然不会触发 b、c 上的事件,所以不输出 b、c1、c2,冒泡更谈不上了,所以不输出 a2

  • 如果点击a,输出什么?(答案是 a1、a2、a3

    不应该是 a1、a3、a2 吗?有同学就会说:“a1、a3可是在捕获阶段被调用的处理程序的,a2 是在冒泡阶段被调用的啊。”这正是要说明的:虽然这三个事件处理程序注册时指定了truefalse,但现在事件流是处于目标阶段,不是冒泡阶段、也不是捕获阶段,事件处理程序被调用的顺序是注册的顺序。不论你指定的是true还是false。换句话来说就是现在点击的是a这个盒子本身,它处于事件流的目标状态,而既非冒泡,又非捕获。(需要注意的是,此时的eventPhase为2,说明事件流处于目标阶段。当点击a的时候,先从document捕获,然后一步步往下找,找到a这个元素的时候,此时的targetcurrentTarget是一致的,所以认定到底了,不需要再捕获了,此时就按顺序执行已经预定的事件处理函数,执行完毕后再继续往上冒泡...)

  • 如果注释掉event.stopImmediatePropagation,点击c,会输出什么?(答案是 a1、a3、a4、b、c1、c2、a2

    如果同一个事件处理程序(指针相同,比如用 handler 保存的事件处理程序),用 addEventListenerattachEvent绑定多次,如果第三个参数是相同的话,也只会被调用一次。当然,如果第三个参数一个设置为true,另一个设置为false,那么会被调用两次。
    而在这里,都是给监听函数的回调赋予了一个匿名函数,所以其实每个处理函数都会被调用。需要注意的是,如果你还不明白为什么在c上触发的先是c1再是c2的话,那么你就需要在去看看第二个问题锁描述的内容了。


以上,就是本文章的内容,文章最后的例子是从网上某个地方看到的,但是具体的出处已经忘记了,如果有同学找到了出处,烦请告知。

参考资料:《JavaScript高级程序设计》

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant