使用 e.stopPropagation() 防止事件冒泡的优点和缺点

Posted

技术标签:

【中文标题】使用 e.stopPropagation() 防止事件冒泡的优点和缺点【英文标题】:Pros and Cons of using e.stopPropagation() to prevent event bubbling 【发布时间】:2013-05-05 17:28:23 【问题描述】:

很多人对此解释说,e.stopPropagation()防止事件冒泡。但是,我有一个很难找到的为什么 STRONG>一个希望或要防止事件冒泡摆在首位。

在我的网站我有很多元素被称为是这样的: P>

$(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'改变this awesome touch library, Hammer.js.。这将允许为用户点击桌面和用于在移动设备上的触摸事件,通常发生的。 P>

与此问题(请纠正我,如果我错了)是滚动在触摸设备上减速至停止。 P>

这是其中e.stopPropgation()是有用吗?使得每当一个触摸屏幕document.body -event鼓泡是不会强>发生每次? P>

【问题讨论】:

您有return false在你的处理器,它已经停止传播... SPAN> 不是 B>想要事件冒泡?或者是:我使用stopPropagation权 SPAN> @bažmegakapa噢,谢谢!好的。这是一个坏榜样,我需要纠正... SPAN> @ FritsvanCampen是啊,我的问题是,为什么你的的希望事件冒泡?我想一个原因,我当时的想法是,它可能会导致上,因为潜在的触摸设备的性能问题太多快速“点击”事件(手指移动速度快),它们各冒泡一路攀升至&lt;body&gt; @ timpeterson - 我加了很多下面有关你在你的cmets提出的各种问题,我的答案 SPAN> 【参考方案1】:

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

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

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

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

在您的特定示例中:

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

这是一个委托事件处理程序。它正在寻找传播到document.body 对象,但起源于a.ajaxLink 对象的任何点击事件。在这里,e.stopPropagation() 几乎没有优势,因为事件几乎已经完全传播(它也将上升到 document,但除非您在 document 对象上也有 click 的处理程序,然后有没有理由在这个处理程序中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,可以迅速发生以解决该问题。如果您有一个包含数百或数千个事件处理程序的大型项目,那么在某些情况下使用委托事件处理会更有效,而在实际目标对象上直接事件处理程序会更有效。但是,除了在大型场景中,性能差异可能并不明显。

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

这是一个委托事件处理程序效率非常低的示例。假设您有一个包含数百个对象和事件处理程序的大小合适的网页。您可以使每个事件处理程序成为附加到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

【讨论】:

@TimPeterson - 我在答案末尾添加了更多内容,以解决您是否要停止传播以提高性能的问题。 -@jfriend00,感谢您的出色回答。好吧,我想我理解你。我们可以讨论最后一部分,“相反,委托的事件处理程序......”?所以最好的性能是使用$('.ajaxLink').click(..。理想情况下,如果我有数百个链接,我不会使用$(document.body).on('click', "a.ajaxLink",..,而是更接近每个链接的东西,例如:$('#parentDiv').on('click', "a.ajaxLink",..?主要问题是这些链接中的许多都是动态创建的,因此我无法直接进行事件处理,而是求助于on() 作为一种包罗万象的方法。想法? @timpeterson - 听起来你有大致的想法。对于动态创建的对象,使用委托事件处理是最简单的,但您应该将委托事件处理程序附加到最近的公共父对象,该对象本身不是动态的(通常是一些容器对象)。如果可以避免的话,您不想要的是大量委托事件处理程序都在同一个高级对象(例如 documentdocument.body)上。 @timpeterson - 我添加了一堆相关链接,指向同一主题的其他帖子。 -@jfriend00-这听起来不错,感谢您的帮助。学到了很多。【参考方案2】:

假设你有一个按钮并想要处理一个点击事件:

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

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


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

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

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

【讨论】:

我明白了,但这不是我(或者我认为大多数人的)代码的样子。在所有情况下,我都使用classNames,例如.ajaxLink,它们被命名为触发特定的处理程序。 @timpeterson:如果你的元素有子元素,你会怎么做?我真的怀疑你没有利用事件传播。 我添加了一些 HTML 来帮助我们讨论,因为我对你在说什么感到困惑。在我上面的 HTML 中,我不在乎孩子 &lt;img&gt; 会发生什么。传播不只是去&lt;body&gt; @timpeterson:事件传播和事件委托不是一回事。 -@Blender 我知道它们不一样,但我的.ajaxLink 不是一直点击气泡直到&lt;body&gt;?什么定义了它停止冒泡的位置?【参考方案3】:

尽可能不要使用stopPropagation()

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

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

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

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

在以下示例中,有 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 #3 的 onClick 事件函数中调用 stopPropagation() 来防止在 DIV #2 的情况下触发 onClick 事件。

还要注意,文档结构如下:

document
  document.documentElement
    document.body
      ...

如果没有停止事件传播,它将到达document 对象。然后event.currentTarget 将是document,而event.target 将是document.documentElementdocument.body&lt;body&gt; 元素下的任何子元素。

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

<!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.documentElementdocument.body 或文档上的任何元素,无需调用stopPropagation()

【讨论】:

以上是关于使用 e.stopPropagation() 防止事件冒泡的优点和缺点的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 获取发布请求后如何防止页面刷新?

如何停止 ASP.NET 回发(返回 false、e.stopPropagation、e.cancelBubble)不起作用

jQuery中return false,e.preventDefault(),e.stopPropagation()的区别

jQuery中return false,e.preventDefault(),e.stopPropagation()的区别

e.preventDefault()和e.stopPropagation()以及return false的作用和区别

jQuery js 中return false,e.preventDefault(),e.stopPropagation()的区别(事件冒泡)