如果你删除了一个 DOM 元素,任何以该元素开始的事件是不是会继续冒泡?
Posted
技术标签:
【中文标题】如果你删除了一个 DOM 元素,任何以该元素开始的事件是不是会继续冒泡?【英文标题】:If you delete a DOM element, do any events that started with that element continue to bubble?如果你删除了一个 DOM 元素,任何以该元素开始的事件是否会继续冒泡? 【发布时间】:2011-02-13 12:56:20 【问题描述】:如果我删除了一个用于启动事件气泡的 DOM 元素,或者它的子元素启动了事件气泡,我应该期待什么行为 - 如果元素被删除,它会继续冒泡吗?
例如 - 假设您有一个表格,并且想要检测表格单元格上的点击事件。另一段 JS 执行了一个 AJAX 请求,一旦请求完成,该请求最终将完全替换该表。
如果我单击表格,并且在表格被成功完成的 AJAX 请求替换后立即会发生什么?我问是因为我看到一些点击事件似乎没有冒泡的行为 - 但很难复制。
我正在表的父元素上观看事件(而不是将事件附加到每个 TD),但有时似乎无法到达。
编辑:再次遇到这个问题,终于找到了根源。根本不是事件冒泡问题!有关详细信息,请参阅下面的答案。
【问题讨论】:
有趣的问题。 FWIW,它应该很容易测试:嵌套两个元素,(分别)观察它们的点击,在孩子的点击处理程序中,删除孩子(this.parentNode.removeChild(this)
),看看事件是否冒泡到父母。 (跑出门,否则我自己也试过了。)
我决定留下来试试看,见下文。
【参考方案1】:
经验:这取决于您使用的浏览器; IE 取消了该事件,其他一切(据我所知)继续它。请参阅下面的测试页面和讨论。
理论上: Andy E's head 很有帮助 found DOM2 says the event should continue 因为冒泡应该基于树的初始状态。所以大多数人的行为是正确的,IE在这里是独立的。 Quelle 惊喜。
但是:这是否与您所看到的有关确实是另一个问题。您正在查看对表格的父元素的点击,并且您怀疑在极少数情况下,当您点击表格时,会出现一个 Ajax 完成替换表格的竞争条件,并且点击会丢失。 javascript 解释器中不存在这种竞争条件,因为目前浏览器上的 Javascript 是单线程的。 (不过,工作线程即将到来——哇哦!)但理论上,点击可能会发生并被浏览器中的非 Javascript UI 线程排队,然后 ajax 可以完成并替换元素,然后排队的 UI 事件被处理并且根本不会发生或不会冒泡,因为该元素不再有父元素,已被删除。这是否真的会发生将取决于浏览器的实现。如果您在任何开源浏览器上看到它,您可能会查看它们的源以将 UI 事件排队以供解释器处理。但这与使用代码在事件处理程序中实际删除元素不同,如下所示。
does-bubbling-continue 方面的经验结果:
经过测试的 Chrome 4 和 Safari 4(例如 WebKit)、Opera 10.51、Firefox 3.6、IE6、IE7 和 IE8。 IE 是唯一一个在您删除元素时取消事件(并且在不同版本中始终如此),其他都没有。无论您使用的是 DOM0 处理程序还是更现代的处理程序,这似乎都无关紧要。
更新: 在测试中,IE9 和 IE10 继续该事件,因此 IE 不符合规范在 IE8 处停止。
使用 DOM0 处理程序的测试页面:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Test Page</title>
<style type='text/css'>
body
font-family: sans-serif;
#log p
margin: 0;
padding: 0;
</style>
<script type='text/javascript'>
window.onload = pageInit;
function pageInit()
var parent, child;
parent = document.getElementById('parent');
parent.onclick = parentClickDOM0;
child = document.getElementById('child');
child.onclick = childClickDOM0;
function parentClickDOM0(event)
var element;
event = event || window.event;
element = event.srcElement || event.target;
log("Parent click DOM0, target id = " + element.id);
function childClickDOM0(event)
log("Child click DOM0, removing");
this.parentNode.removeChild(this);
function go()
var write = log;
function log(msg)
var log = document.getElementById('log');
var p = document.createElement('p');
p.innerHTML = msg;
log.appendChild(p);
</script>
</head>
<body><div>
<div id='parent'><div id='child'>click here</div></div>
<hr>
<div id='log'></div>
</div></body>
</html>
使用attachEvent
/addEventListener
处理程序的测试页面(通过原型):
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Test Page</title>
<style type='text/css'>
body
font-family: sans-serif;
#log p
margin: 0;
padding: 0;
</style>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js'></script>
<script type='text/javascript'>
document.observe('dom:loaded', pageInit);
function pageInit()
var parent, child;
parent = $('parent');
parent.observe('click', parentClick);
child = $('child');
child.observe('click', childClick);
function parentClick(event)
log("Parent click, target id = " + event.findElement().id);
function childClick(event)
log("Child click, removing");
this.remove();
function go()
var write = log;
function log(msg)
$('log').appendChild(new Element('p').update(msg));
</script>
</head>
<body><div>
<div id='parent'><div id='child'>click here</div></div>
<hr>
<div id='log'></div>
</div></body>
</html>
【讨论】:
@T.J.精彩的细节和总结,完美的答案,可惜我不能再给一个 +1 哈哈。 我今天又碰到了这个问题,终于搞清楚是怎么回事了!以为你会感兴趣。在下面发布我自己的答案。【参考方案2】:自从我最初发布这个问题以来已经有一段时间了。尽管 T.J.Crowder 的回答非常翔实(就像 Andy E 的回答一样),并告诉我它应该有效,但我仍然发现问题。我搁置了一段时间,但今天在另一个 Web 应用程序中再次遇到同样的问题时重新访问它。
我玩了一段时间,我开始意识到如何每次都重复这个问题(至少在 FF3.6 和 Chrome 8 中)。问题不在于事件气泡被取消,或者在移除 DOM 元素时丢失。相反,问题在于,如果元素在 在 mousedown 和 mouseup 之间更改,则不会触发“点击”。
根据Mozilla Development Network:
当用户点击一个元素时触发点击事件。 click 事件将在 mousedown 和 mouseup 事件之后发生。
因此,当您的 DOM 元素发生变化时,您可能会遇到这个问题。我错误地认为事件泡沫正在消失。碰巧的是,如果您有一个经常更新的元素,您会更频繁地看到它(这是我的情况)并且不太可能将它作为侥幸传递出去。
附加测试(见 jsfiddle 上的example)表明,如果单击,按住按钮并等待 DOM 元素发生变化,然后释放按钮,我们可以观察到(在 jquery 实时上下文中):
“点击”事件不会触发 第一个节点触发“mousedown”事件 为更新的节点触发“mouseup”事件编辑:在 IE8 中测试。 IE8 为第一个节点触发 mousedown,为更新的节点触发 mouseup,但 确实 实际上触发了“点击”,使用更新的节点作为事件源。
【讨论】:
有趣的东西。似乎 Internet Explorer 也有它。检查DOM3 Events draft spec -- “点击事件可能在同一元素上的 mousedown 和 mouseup 事件之前发生,忽略其他节点类型(例如,文本节点)之间的变化。” - 基本上是说只要鼠标下最上面的元素保持不变,点击事件就应该触发。但是,如果 innerHTML 发生了变化,它不应该发生变化。 两年后再次发现了这个问题,这个问题在最新的 Firefox 中得到了修复(尽管你的 Fiddle 需要一些工作),但遗憾的是 Webkit 没有。可能值得发布错误报告! 非常感谢您的解决方案和解释。几天来,我一直在对这种行为进行故障排除,我有一个经常更改的 DOM 元素,并且 onclick 事件有时只是没有触发。现在使用 onmouseup 而不是 onclick,它们 100% 的时间都会触发。我猜如果 mousedown 和 mouseup 的 DOM 元素不匹配,则会导致歧义,并且浏览器宁愿不触发 onclick 如果它会产生歧义,但仍然会触发不同版本的更新的 mouseup 和 mousedown DOM。【参考方案3】:是的,它应该继续传播。除了target
属性之外,事件与其触发的事件没有真正的关联。当您删除元素时,传播事件的内部代码不应该有任何“意识”原始元素已从可见文档中消失。
顺便说一句,使用removeChild
不会立即删除一个元素,它只是将它从文档树中分离出来。一个元素只有在没有引用时才应该被删除/垃圾收集。因此,该元素仍有可能通过event.target
属性被引用,甚至在被垃圾回收之前被重新插入。 我没试过,所以只是猜测。
T.J.克劳德的评论让我决定举一个简单的例子。我在这两个方面都是正确的,它确实会冒泡,您仍然可以使用
event.target
获得对已删除节点的引用。
http://jsbin.com/ofese/2/
作为 T.J.发现,在IE中并非如此。但是 DOM 2 级事件规范 does define it as correct behavior [强调我的]:。
被指定为冒泡的事件最初将按照与非冒泡事件相同的事件流进行。该事件被分派到它的目标 EventTarget 并触发在那里找到的任何事件侦听器。然后,冒泡事件将触发通过向上跟踪 EventTarget 的父链找到的任何其他事件侦听器,检查在每个连续 EventTarget 上注册的任何事件侦听器。这种向上传播将持续到并包括文档。在此阶段不会触发注册为捕获器的 EventListener。 从事件目标到树顶部的 EventTargets 链是在事件初始调度之前确定的。如果在事件处理过程中对树进行了修改,则事件流将根据树的初始状态进行。
【讨论】:
@Andy:实际上,不,这取决于浏览器。我决定留下来试试看,看看我的答案。 @T.J.大声笑我知道我应该测试 IE。我想,彻底的付出是值得的,为你+1!尽管如此,我还是不明白为什么 IE 会这样做(尽管它并没有让我感到那么惊讶),如果你问我,这违背了所有逻辑。就像我在回答中所说的那样,使用removeChild
,元素并没有完全删除,只是从 DOM 树中分离出来,可以重新放入。取消事件是没有意义的。
@Andy:是的,总是用 IE 测试,很多东西都不一样。 :-) 我可以看到他们的解释,使用这个(完全假设的逻辑): 1.调度程序接收事件 2.调度程序在元素 3 上调度事件。处理程序删除元素 4.调度程序检查 cancelBubble
标志并且它是错误的 5.调度程序看着element.parentNode
——但它是null
;停止派送。而其他浏览器似乎在 开始调度事件之前获取了整个祖先。除非规范明确说明应该发生什么,否则我认为这两种行为都不是错误的。 :-)
@Andy:太好了,感谢您的报价和参考!可怜的微软。 :-) 这似乎也是定义冒泡的最佳方式,很高兴他们在 DOM2 中做到了。
在 DOM2 事件规范中很好的发现。 +1以上是关于如果你删除了一个 DOM 元素,任何以该元素开始的事件是不是会继续冒泡?的主要内容,如果未能解决你的问题,请参考以下文章