20

如果我删除用于启动事件气泡的 DOM 元素,或者其子元素启动了事件气泡,我应该期待什么行为 - 如果元素被删除,它会继续冒泡吗?

例如 - 假设您有一个表格,并且想要检测表格单元格上的点击事件。另一段 JS 执行了一个 AJAX 请求,一旦请求完成,该请求最终将完全替换表。

如果我单击表格,并且在表格被成功完成的 AJAX 请求替换后会立即发生什么?我问是因为我看到一些点击事件似乎没有冒泡的行为 - 但很难复制。

我正在表的父元素上观看事件(而不是将事件附加到每个 TD),但有时似乎无法到达。

编辑:再次遇到这个问题,终于找到了它的根源。根本不是事件冒泡问题!有关详细信息,请参阅下面的答案。

4

3 回答 3

24

凭经验:这取决于您使用的浏览器;IE 取消了该事件,其他一切(据我所知)继续它。请参阅下面的测试页面和讨论。

理论上: Andy E 的负责人发现DOM2表示事件应该继续,因为冒泡应该基于树的初始状态。所以大多数人的行为是正确的,IE在这里是独立的。奎尔惊喜。

但是:这是否与您所看到的有关确实是另一个问题。您正在查看对表格的父元素的点击,您怀疑在极少数情况下,当您点击表格时,会出现一个 Ajax 完成替换表格的竞争条件,并且点击会丢失。Javascript 解释器中不存在这种竞争条件,因为目前浏览器上的 Javascript 是单线程的。(不过,工作线程即将到来——哇哦!)但理论上,点击可能会发生并由浏览器中的非 Javascript UI 线程排队,然后 ajax 可以完成并替换元素,然后排队的 UI 事件被处理并且根本不会发生或不会冒泡,因为该元素不再有父元素,已被删除。很多关于浏览器的实现。如果您在任何开源浏览器上看到它,您可能会查看它们的源以将 UI 事件排队以供解释器处理。但这与在事件处理程序中使用代码实际删除元素不同,如下所示。

冒泡继续方面的经验结果:

测试了 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>
于 2010-04-28T20:39:24.743 回答
6

自从我最初发布这个问题以来已经有一段时间了。尽管 TJCrowder 的回答提供了很多信息(就像 Andy E 的回答一样),并告诉我它应该可以工作,但我仍然看到一个问题。我把它搁置了一段时间,但今天当我在另一个 Web 应用程序中再次遇到同样的问题时重新访问它。

我玩了一会儿,我开始意识到如何每次都重复这个问题(至少在 FF3.6 和 Chrome 8 中)。问题不在于事件气泡被取消,或者在移除 DOM 元素时丢失。相反,问题在于,如果元素在 mousedown 和 mouseup之间发生变化,则不会触发“点击”。

根据Mozilla 开发网络

当用户点击一个元素时会引发 click 事件。click 事件将在 mousedown 和 mouseup 事件之后发生。

因此,当您的 DOM 元素发生变化时,您可能会遇到这个问题。我错误地认为事件泡沫正在消失。碰巧的是,如果您有一个经常更新的元素,您会更频繁地看到它(这是我的情况)并且不太可能将其作为侥幸传递出去。

附加测试(参见 jsfiddle 上的示例)表明,如果单击,按住按钮并等待 DOM 元素更改,然后释放按钮,我们可以观察到(在 jquery 实时上下文中):

  • “点击”事件不会触发
  • 第一个节点触发 'mousedown' 事件
  • 'mouseup' 事件为更新的节点触发

EDIT: Tested in IE8. IE8 fires mousedown for first node, mouseup for updated node, but does in fact fire 'click', using the updated node as the event source.

于 2011-01-05T17:12:11.793 回答
5

是的,它应该继续传播。事件与其触发的事件没有真正的联系,除了target属性。当您删除元素时,传播事件的内部代码不应该有任何“意识”原始元素已从可见文档中消失。

顺便说一句, usingremoveChild不会立即删除元素,它只是将其从文档树中分离出来。一个元素只有在没有引用时才应该被删除/垃圾收集。因此,可能仍然可以通过该event.target属性引用该元素,甚至在被垃圾回收之前重新插入该元素。 不过我没试过,所以只是猜测。


TJ Crowder 的评论让我决定举一个简单的例子。我在这两个方面都是正确的,它确实会冒泡,您仍然可以使用event.target.

http://jsbin.com/ofese/2/


正如 TJ 发现的那样,在 IE 中情况并非如此。但是 DOM 2 级事件规范确实将其定义为正确的行为[强调我的]:。

被指定为冒泡的事件最初将按照与非冒泡事件相同的事件流进行。该事件被分派到它的目标 EventTarget 并触发在那里找到的任何事件侦听器。然后,冒泡事件将触发通过向上跟踪 EventTarget 的父链找到的任何其他事件侦听器,检查在每个后续 EventTarget 上注册的任何事件侦听器。这种向上传播将持续到并包括文档。在此阶段不会触发注册为捕获器的 EventListener。从事件目标到树顶部的 EventTargets 链是在事件的初始分派之前确定的。如果在事件处理过程中对树进行了修改,则事件流将根据树的初始状态进行。

于 2010-04-28T20:21:00.907 回答