zoukankan      html  css  js  c++  java
  • 【转】JavaScript 事件顺序:冒泡和捕获

    补充说明:这篇文章通俗易懂地讲解了冒泡和捕获原理,原文来自 ppk 大侠的 quirksmode 站点。感谢网友 hh54188 的翻译。

    事件的发生顺序

    这个问题的起源非常简单,假设你在一个元素中又嵌套了另一个元素

    -----------------------------------
    | element1                        |
    |   -------------------------     |
    |   |element2               |     |
    |   -------------------------     |
    |                                 |
    -----------------------------------
    

    并且两者都有一个 onClick 事件处理句柄 (event handler)。如果用户单击元素 2,则元素 1 和元素 2 的单击事件都会被触发。但是哪一个事件先被触发?哪一个事件处理函数会被首先执行?换句话说,事件的发生顺序到底如何?

    两种模型

    不出所料,在那些“不堪回首”(浏览器大战)的日子里,Netscape 和微软有两种截然不同的处理方法:

    • Netscape 主张元素 1 的事件首先发生,这种事件发生顺序被称为捕获型
    • 微软则保持元素 2 具有优先权,这种事件顺序被称为冒泡型

    这两种事件顺序是截然相反的。Explorer浏览器只支持冒泡事件,Mozilla,Opera7 和 Konqueror 两者都支持。而更古老的 opera 和 iCab 两者都不支持

    捕获型事件

    当你使用捕获型事件时

                   | |
    ---------------| |-----------------
    | element1     | |                |
    |   -----------| |-----------     |
    |   |element2   /          |     |
    |   -------------------------     |
    |        Event CAPTURING          |
    -----------------------------------
    

    元素 1 的事件处理函数首先被触发,元素 2 的事件处理函数最后被触发

    冒泡型事件

    当你使用冒泡型事件时

                   / 
    ---------------| |-----------------
    | element1     | |                |
    |   -----------| |-----------     |
    |   |element2  | |          |     |
    |   -------------------------     |
    |        Event BUBBLING           |
    -----------------------------------
    

    元素 2 的处理函数首先被触发,元素 1 其次

    W3C 模型

    W3c 明智的在这场争斗中选择了一个择中的方案。任何发生在w3c事件模型中的事件,首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段

                     | |  / 
    -----------------| |--| |-----------------
    | element1       | |  | |                |
    |   -------------| |--| |-----------     |
    |   |element2     /  | |          |     |
    |   --------------------------------     |
    |        W3C event model                 |
    ------------------------------------------
    

    为一个 web 开发者,你可以选择是在捕获阶段还是冒泡阶段绑定事件处理函数,这是通过 addEventListener() 方法实现的,如果这个函数的最后一个参数是 true,则在捕获阶段绑定函数,反之 false,在冒泡阶段绑定函数。

    假设你要做

    element1.addEventListener('click',doSomething2,true)
    
    element2.addEventListener('click',doSomething,false)
    

    如果用户单击元素2,则接下来会发生:

    (事件在这里就像一个观光客,由外至内游览,逐渐接近被触发的主要元素,然后又反向离开)

    1. 单击事件首先进入捕获阶段开始(逐渐接近元素 2 的方向)。查看元素 2 的祖先元素中是否有在捕获阶段有 onclick 处理函数的
    2. 发现元素 1 有一个,于是 doSomething2 被执行
    3. 事件检查到目标自己(元素 2),捕获阶段没有发现更多的处理函数了。事件开始进入冒泡阶段,想当然执行 doSomething(),这个绑定于元素 2 冒泡阶段的函数。
    4. 事件向远离元素 2 的方向,查看是否有任何祖先元素在冒泡阶段绑定了一个处理函数。没有这样的情况,所以什么也没有发生

    相反的情况是:

    element1.addEventListener('click',doSomething2,false)
    element2.addEventListener('click',doSomething,false)
    

    现在如果用户点击元素 2 会发生:

    1. 单击事件进入捕获阶段。查看元素 2 的祖先元素中是否有在捕获阶段有 onclick 处理函数的,结果一无所获
    2. 事件检查到目标自己。事件开始进入冒泡阶段,并且执行绑定于元素2冒泡阶段的函数。doSomething()
    3. 事件开始远离目标,检查元素 2 的祖先元素中是否有在冒泡阶段绑定了处理函数的
    4. 发现了一个,于是元素 1 的 doSomething2() 被执行

    兼容性和传统模式

    在支持 w3c dom (文档对象模型) 的浏览器中,传统的事件绑定方法是

    element1.onclick = doSomething2;
    

    默认被视为在绑定于冒泡阶段

    使用冒泡型事件

    很少的开发人员会有意识的去使用冒泡型事件或者捕获型事件。在他们今天制作的网页中,没有必要让一个事件因为冒泡而被好几个函数处理。但是有时用户 通常会很疑惑,因为在他们只点击了一次鼠标之后出现了许多种情况(多个函数被执行,因为冒泡)。而大多数情况下你还是希望你的处理函数相互独立的。当用户 点击了某一个元素,发生什么,点击另一个元素,又对应发生些什么,相互独立,而不因为冒泡连锁。

    一直在发生

    首先你要明白的是事件捕获或者冒泡一直在发生。如果你给整个页面文档的定义一个通用 onclick 处理函数

    document.onclick = doSomething;
    if (document.captureEvents) document.captureEvents(Event.CLICK);
    

    在页面上单击任何元素的单击事件,最终会冒泡至页面最高文档层,因此触发那个通用的处理函数,除非之前一个处理函数明确的指出终止冒泡,这样才冒泡才不会传播到整个文档层面

    用法(这一小节翻译的不好,因为没有实战,我也不是很理解,可以在留言中补充,我会更新)

    因为任何事件传播终止于页面文档(这个最高层),这使默认的事件处理函数变得可能,假设你有这样一个页面

    ------------------------------------
    | document                         |
    |   ---------------  ------------  |
    |   | element1    |  | element2 |  |
    |   ---------------  ------------  |
    |                                  |
    ------------------------------------
    element1.onclick = doSomething;
    element2.onclick = doSomething;
    document.onclick = defaultFunction;
    

    现在如果用户单击元素 1 或者元素 2,doSomething() 将被执行。如果你愿意的话,如果你不想让事件冒泡至执行 defaultFunction(),你可以在这里阻止事件冒泡向上传播,。但是如果用户点击页面上的其他部位,defaultFunction() 还是 会被执行。这样的效果或许有时能用的上。

    设置页面­——使处理函数有范围较大的触发面积,在“拖拽效果”脚本中是必须的。一般来说在某一个元素层上发生 mousedown事件意味着选择了这个元素,并且使它能够响应 mousemove 事件。虽然 mousedown 通常绑定于这个元素层上以避免浏览器 bug,但是其他两者的事件函数的范围必须是整个页面(?)

    记住浏览器学的第一法则(First Law of Browserology)是:一切皆有可能(anything can happen),并且是在你起码有点准备的时候。所以有可能发生的是,用户拖拽时,大幅度在页面上移动他的鼠标,脚本却不能在大幅度中做出反应,以至于鼠 标也就不再停留在元素层上了

    • 如果 onmouseover 处理函数绑定在元素层上,这个元素层不会再对鼠标的移动有任何反应,这会让用户觉得奇怪
    • 如果 onmouseup 处理函数绑定在元素层上,事件也不能被触发,后果是,用户想放下这个元素层后,元素层持续对鼠标移动做出反应。这会引起(用户)更多的迷惑(?)

    所以在这个例子中,事件冒泡非常的有用,因为将你的处理函数放在页面层能保证他们一直能被执行

    把它给关了

    但是一般情况下,你会想关了所有的冒泡和捕获以保证函数之间不会打扰到对方。除此之外,如果你的文档结构相当的复杂(许多 table 之间相互嵌套或 者诸如此类),你也会为了节省系统资源,而关闭冒泡。此时浏览器不得不检查目标元素的每一个祖先,看是否它有一个处理函数。即使一个都没有找到,刚刚的搜 索同样花费不少时间

    在微软的模型中,你必须设置事件的 cancelBubble 的属性为 true

    window.event.cancelBubble = true
    

    在 w3c 模型中你必须调用事件的 stopPropagation() 方法

    e.stopPropagation()
    

    这会阻止所有冒泡向外传播。而作为跨浏览器解决方案应该这么作:

    function doSomething(e)
    
    {
          if (!e) var e = window.event;
    
             e.cancelBubble = true;
    
             if (e.stopPropagation) e.stopPropagation();
    
    }
    

    在支持 cancelBubble 属性的浏览器中设置 cancelBubble 无伤大雅。浏览器会耸一耸肩然后创造一个这个属性。当然这也并不能真正的取消冒泡,但至少能保证这条命令是安全正确的

    currentTarget

    像我们之前看到的一样,一个事件用 target 或者是 srcElement 属性用来表示事件究竟发生在哪个目标元素上(即用户最初点击的元素)。在我们的例子中是元素 2,因为我们单击了它。

    非常重要的是,要明白在捕获或者冒泡阶段的目标元素是不变的,它始终与元素 2 相关联。

    但是假设我们绑定了以下函数

    element1.onclick = doSomething;
    element2.onclick = doSomething;
    

    如果用户单击元素2, doSomething() 会被执行两次。但是你怎么知道哪个 html 元素正在响应这个事件?target/srcElement 也没有给出线索,但人们总是更倾向于元素2,因为它是引起事件的原因(因为用户点击的是它)。

    为了解决这个问题,w3c 增加了 currentTarget 这个属性,它就指向正在处理事件的元素:这恰是我们需要的。很不幸的是微软模型中并没有相似的属性

    你也可以使用”this”关键字。在上面的例子中,它相当于正在处理事件的 html 元素,就像 currentTarget。

    微软模型的问题

    但是当你使用微软事件绑定模型时,this 关键字并不相当于 HTML 元素。联想缺少类似 currentTarget 属性的微软模型(?)——按上面的代码操作的话,你这么做便意味着:

    element1.attachEvent('onclick',doSomething)
    element2.attachEvent('onclick',doSomething)
    

    你无法确切知道哪一个 HTML 元素正在负责处理事件,这是微软事件绑定模型最严重的问题,对我来说,这也是我从不使用它的原因,哪怕是在开发仅供 Windows 下的 IE 的应用程序

    我希望能够尽快增加 currentTarget 类似的属性——或者遵循标准?web 开发者们需要这些信息

    来源:qingbob

  • 相关阅读:
    let 和 const 命令
    字符串和数组之间相互转换
    angularjs 添加拦截器
    js判断数组是否包含指定元素的方法
    angularjs ng-include
    requirejs学习 关于requirejs的一些参数问题
    ngDialog 点击窗口以外不允许关闭弹窗
    jQuery Jcrop 图像裁剪
    Javascript模块化编程(三):require.js的用法
    angular.bootstrap手动加载angularjs模块
  • 原文地址:https://www.cnblogs.com/snowinmay/p/3230017.html
Copyright © 2011-2022 走看看