许多人都解释说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 事件冒泡?
答案 0 :(得分:12)
有几种方法可以处理javascript / jQuery中的事件。其中两个是:
如果您在对象上使用直接事件处理程序,并且页面中没有配置委派事件处理程序,则没有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()
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">↓</span>
</button>
如果你没有使用.arrow
的事件处理程序停止事件传播,它也会触发按钮的事件处理程序。
答案 2 :(得分:0)
如果可能,请勿使用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#2阻止触发onClick事件,则无需在DIV#3的onClick事件函数中调用stopPropagation()
。
另请注意,文档结构如下:
document
document.documentElement
document.body
...
如果事件传播未停止,它将到达document
对象。 event.currentTarget
将为document
,而event.target
将为document.documentElement
,document.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()
。