使用e.stopPropagation()来防止事件冒泡的利弊

时间:2013-05-10 23:39:32

标签: javascript jquery javascript-events event-bubbling stoppropagation

许多人都解释说e.stopPropagation()可以防止事件冒泡。但是,我很难找到为什么人们想要或者想要首先阻止事件冒泡。

在我的网站上,我有许多这样的元素:

$(document.body).on('click', ".clickable", function(e){ 
   //e.stopPropagation();
  //do something, for example show a pop-up or click a link   
});

<body>
  <p>outside stuff</p>
  <button type="button" class='clickable'>
    <img src='/icon.jpg'> Do Something

  </button>
</body>

我想添加e.stopPropagation()因为我想使用this awesome touch library, Hammer.js.将事件处理程序从'touch'更改为'click'。这样可以在桌面上正常点击,也可以在移动设备上进行触摸事件。

这个问题(如果我错了请纠正我)是触摸设备上的滚动速度会慢下来。

这是e.stopPropgation()有用的地方吗?这样每当有人触摸屏幕document.body时 - 每次都会发生 NOT 事件冒泡?

3 个答案:

答案 0 :(得分:12)

有几种方法可以处理javascript / jQuery中的事件。其中两个是:

  1. 您可以在对象上使用直接事件处理程序。
  2. 您可以使用委托事件处理来处理父项上的传播事件。
  3. 如果您在对象上使用直接事件处理程序,并且页面中没有配置委派事件处理程序,则没有e.stopPropagation()的原因。

    但是,如果您委派了使用传播的事件处理程序,您有时希望确保更高级别的委托事件处理程序不会触发当前事件。

    在您的特定示例中:

    $(document.body).on('click', "a.ajaxLink", function(e){ 
    

    这是一个委托的事件处理程序。它正在查找传播到document.body对象的任何单击事件,该事件源自a.ajaxLink对象。在这里,e.stopPropagation()几乎没有什么优势,因为事件几乎已经完全传播(它也会升级到document,但除非你在click上有document的处理程序{1}}对象,然后在此处理程序中没有e.stopPropagation()的理由。

    如果您同时拥有顶级委托事件处理程序(如示例中的那个)并且您具有较低级别的事件处理程序(直接在对象上或使用委托事件处理,那么它会有很多意义)但是在低于document.body对象的级别。在这种情况下,如果您只希望较低级别的事件处理程序获取事件,那么您可以在其处理程序中调用e.stopPropagation(),以便document.body处理程序从未见过该事件。

    $("a.ajaxLink").click(function(e) {
        if (some condition) {
            // do something specific to this condition
            code here
            // stop propagation so the default behavior for click in document.body does not fire
            e.stopPropagation();
        }
    })
    

    注意:使用jQuery事件处理程序中的return false会触发e.stopPropagation()e.preventDefault()。但是,如果您在委派的事件处理程序中,e.preventDefault()没有做任何事情,因为当目标对象第一次看到事件时,已经触发了默认行为(如果有的话)。默认行为发生在事件传播之前,因此e.preventDefault()仅适用于直接在目标对象上的事件处理程序。


    没有明显的性能下降,因为你允许一个事件冒泡,因为这些是用户级别的事件,并且它们不会发生足够快的事情,而不是当没有处理程序时冒泡特别慢干预对象。系统已经有一些特殊情况,例如mousemove等可以快速解决该问题的事件。如果您有一个包含数百或数千个事件处理程序的巨型项目,则有时使用委派事件处理更有效,并且有些情况下实际目标对象上的直接事件处理程序更有效。但是,除了巨大的场景外,性能差异可能并不明显。

    以下是冒泡/委派更有效的示例。你有一个包含数千行的巨型表,每行有两个按钮(添加/删除说)。通过委派事件处理,您可以处理附加到表对象的两个简单事件处理程序中的所有按钮(按钮的公共父级)。这将更快地安装事件处理程序,而不是直接在每个按钮上安装数千个事件处理程序。这些委派的事件处理程序也将自动处理表中新创建的行/对象。这是事件冒泡/委托事件处理程序的理想方案。请注意,在这种情况下,没有理由停止传播/冒泡。

    这是委托事件处理程序效率非常低的示例。假设您有一个包含数百个对象和事件处理程序的大型Web页面。您可以使每个事件处理程序都是附加到document对象的委托事件处理程序。但是,现在发生了什么。点击发生。实际对象上没有事件处理程序,因此它会冒泡。最终,它到达文档对象。文档对象有数百个事件处理程序。事件处理引擎(在本例中为jQuery)必须查看每个事件处理程序,并将委托事件处理程序中的选择器与原始事件目标进行比较,以查看它们是否匹配。其中一些比较并不快,因为它们可以是完整的CSS选择器。它必须为数百个委派事件做到这一点。这对性能不利。这正是jQuery中.live()被弃用的原因,因为它以这种方式工作。相反,委托事件处理程序应尽可能靠近目标对象放置(根据具体情况,最接近的父级)。并且,当不需要委托事件处理程序时,处理程序应该放在实际的目标对象上,因为这在运行时是最有效的。


    回到原来的问题。没有时间你想要一般关闭冒泡。正如我在前面的回答中所描述的那样,有一些特定的实例,树上的事件处理程序想要处理事件并阻止DOM树中更高级别的任何委托事件处理程序处理此事件。这是e.stopPropatation()的时间。


    以下是其他一些相关帖子,其中包含有关此主题的有用信息(之前已广泛讨论过):

    Why not take Javascript event delegation to the extreme?

    Should all jquery events be bound to $(document)?

    Does jQuery.on() work for elements that are added after the event handler is created?

    jQuery on() and stopPropagation()

    Best practice to avoid memory or performance issues related to binding a large number of DOM objects to a click event

    jQuery .live() vs .on() method for adding a click event after loading dynamic html

答案 1 :(得分:2)

想象一下,您有一个按钮,想要处理点击事件:

<button>
    <img src="icon" />
    <span>Text</span>
</button>

如果没有事件传播,单击图像或文本将不会触发绑定到按钮的单击事件处理程序,因为事件永远不会离开<img><span>元素。


您不希望事件传播的情况是嵌套元素具有自己的事件处理程序。这是一个例子:

<button>
    <img src="icon" />
    <span>Text</span>
    <span class="arrow">&darr;</span>
</button>

如果你没有使用.arrow的事件处理程序停止事件传播,它也会触发按钮的事件处理程序。

答案 2 :(得分:0)

如果可能,请勿使用stopPropagation()

使用stopPropagation()的两个好处是:

  • 编写代码(独立事件函数)
  • 更容易
  • 性能

虽然这个函数似乎很有用,但它可以被认为是错误的编码风格,特别是当你无法完全控制代码时(例如,因为你使用的是第三方库)。 stopPropagation()是一个全有或全无的概念。它不允许精细控制流程。如果两个其他嵌套元素之间的某个元素停止了事件传播,那么任何父元素都不会接收它,尽管可能存在情况,当它们 接收它时。

解决此问题的一种更优雅(而不是那么复杂)的方法是始终通过永远不会调用stopPropagation()来允许事件传播。定义自己不会从子元素自动执行的事件的元素可以使用targetcurrentTarget属性来检查初始事件的来源,并仅在以下情况下执行自己的事件函数。它是通缉。

在下面的示例中,有3个嵌套的DIV。单击最低(蓝色)DIV将通过整个DOM结构向上传播onClick事件,但不调用位于以下之间的绿色DIV的onClick事件:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <style>
            div {
                min-width: 100px;
                min-height: 50px;
                display: inline-block;
                padding: 2em;
            }
            #div1 { background: red; }
            #div2 { background: green; }
            #div3 { background: blue; }
            #info { display: block; white-space: pre; }
        </style>
        <script>
            window.addEventListener('load', function() {

                // #div1, #div3
                document.querySelector('#div1').addEventListener('click', function(e) {
                    if (e.target == e.currentTarget ||
                        e.target == document.querySelector('#div3')) {
                        document.querySelector('#info').textContent +=
                            'I am #1 or #3\n';
                    }
                });

                // #div2
                document.querySelector('#div2').addEventListener('click', function(e) {
                    if (e.currentTarget == e.target) {
                        document.querySelector('#info').textContent += 'I am #2\n';
                    }
                });
            });
        </script>
    </head>
    <body>
        <div id="div1">
            <div id="div2">
                <div id="div3"></div>
            </div>
        </div>
        <div id="info"></div>
    </body>
</html>

因此,如果DIV#2阻止触发onClick事件,则无需在DIV#3的onClick事件函数中调用stopPropagation()

另请注意,文档结构如下:

document
  document.documentElement
    document.body
      ...

如果事件传播未停止,它将到达document对象。 event.currentTarget将为document,而event.target将为document.documentElementdocument.body<body>元素下的任何子元素。

所以考虑到你有以下代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <style>
            html {
                background: #009688;
            }
            body {
                background: #bbb;
            }
        </style>
    </head>
    <body>
        <div>Hello world</div>
        <div>Hello world</div>
        <div>Hello world</div>
        <div>Hello world</div>
        <div>Hello world</div>
        <div>Hello world</div>
    </body>
</html>

以下是它的样子以及文档的不同部分: Simple page

灰色是身体区域。绿色是实际文档“元素”(最顶部的样式部分)。在它背后,有一个看不见的document对象。

如果你想使用直接在你的手指/鼠标光标下执行 的事件函数,你可以使用下面的代码(onClick事件的例子):

elm.addEventListener('click', function(e) {
    if
    (
        (
            (e.currentTarget == document) &&
            (e.target == document.documentElement || e.target == document.body)
        )
        ||
        (e.currentTarget == e.target)
    )
    {
        // ...
    }
});

它适用于document.documentElementdocument.body或文档中的任何元素,无需调用stopPropagation()