8

许多人解释说,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(),因为我想将事件处理程序更改为'touch'使用'click'这个很棒的触摸库 Hammer.js。. 这将允许点击在桌面上正常发生,并允许在移动设备上进行触摸事件。

这个问题(如果我错了,请纠正我)是触摸设备上的滚动速度减慢到停止。

这哪里e.stopPropgation()有用?这样每当一个人触摸屏幕时,document.body事件冒泡就不会每次都发生?

4

3 回答 3

15

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

  1. 您可以在对象上使用直接事件处理程序。
  2. 您可以使用委托事件处理来处理父级上的传播事件。

如果您在对象上使用直接事件处理程序并且页面中没有配置委托事件处理程序,则没有理由使用e.stopPropagation().

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

在您的特定示例中:

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

这是一个委托事件处理程序。它正在寻找任何传播到document.body对象但起源于a.ajaxLink对象的点击事件。在这里,几乎没有什么好处,因为事件几乎已经e.stopPropagation()完全传播(它也会上升到documentclickdocumente.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();
    }
})

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


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

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

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


回到你原来的问题。没有时间通常要关闭冒泡。正如我在前面的回答中所描述的,在某些特定情况下,树上更远的事件处理程序想要处理事件并阻止 DOM 树中更高层的任何委托事件处理程序处理此事件。那是时候了e.stopPropatation()


以下是其他几篇相关文章,其中包含有关此主题的有用信息(正如之前已广泛讨论的那样):

为什么不将 Javascript 事件委托发挥到极致呢?

所有 jquery 事件都应该绑定到 $(document) 吗?

jQuery.on() 是否适用于在创建事件处理程序后添加的元素?

jQuery on() 和 stopPropagation()

避免与将大量 DOM 对象绑定到单击事件相关的内存或性能问题的最佳实践

jQuery .live() 与 .on() 方法在加载动态 html 后添加点击事件

于 2013-05-10T23:49:44.293 回答
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' 事件处理程序停止事件传播,它也会触发按钮的事件处理程序。

于 2013-05-10T23:44:07.163 回答
0

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

使用的两个优点stopPropagation()是:

  • 更容易编写代码(独立的事件函数)
  • 表现

虽然这个函数看起来很有用,但它可能被认为是糟糕的编码风格,尤其是当您无法完全控制代码时(例如,因为您使用第三方库)。stopPropagation()是一个无所不包的概念。它不允许精细控制流。如果其他两个嵌套元素之间的某个元素停止了事件传播,则任何父元素都不会接收它,尽管可能存在应该接收它的情况。

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

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

<!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>

因此stopPropagation(),如果 DIV #2,不需要调用 DIV #3 的 onClick 事件函数来防止触发 onClick 事件。

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

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>

这是它的外观以及文档不同部分的位置: 简单页面

灰色是身体区域。绿色是实际的文档“元素”(最可样式化的部分)。而在它的背后,是看不见的document物体。

如果你想使用事件函数,它只对你手指/鼠标光标下的元素执行,你可以使用下面的代码(例如 onClick 事件):

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

它可以与document.documentElement,document.body或文档上的任何元素一起使用,而无需调用stopPropagation().

于 2017-10-28T04:01:46.510 回答