如果删除 DOM 元素,以该元素开头的任何事件是否会继续冒泡?

发布于 2024-08-30 20:31:09 字数 361 浏览 9 评论 0原文

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

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

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

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

编辑:再次遇到这个问题,终于找到了根源。根本不是事件冒泡问题!详情请参阅下面我的回答。

What behavior should I expect if I delete a DOM element that was used to start an event bubble, or whose child started the event bubble - will it continue to bubble if the element is removed?

For example - lets say you have a table, and want to detect click events on the table cells. Another piece of JS has executed an AJAX request that will eventually replace the table, in full, once the request is complete.

What happens if I click the table, and immediately after the table gets replaced by a successful completion of an AJAX request? I ask because I am seeing some behavior where the click events don't seem to be bubbling - but it is hard to duplicate.

I am watching the event on a parent element of the table (instead of attaching the event to every TD), and it just doesn't seem to reach it sometimes.

EDIT: Encountered this problem again, and finally got to the root of it. Was not a event-bubbling issue at all! See my answer below for details.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

拥醉 2024-09-06 20:31:09

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

理论上: Andy E 的头很有帮助发现 < a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow-bubbling" rel="nofollow noreferrer">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 处理程序的测试页面(通过 Prototype):

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

Empirically: It depends on what browser you're using; IE cancels the event, everything else (as far as I can tell) continues it. See the test pages and discussion below.

Theoretically: Andy E's head helpfully found that DOM2 says the event should continue because bubbling should be based on the initial state of the tree. So the behavior of the majority is correct, IE's on its own here. Quelle surprise.

But: Whether that relates to what you're seeing is another question indeed. You're watching for clicks on a parent element of the table, and what you suspect is that very rarely, when you click the table, there's a race condition with an Ajax completion that replaces the table and the click gets lost. That race condition can't exist within the Javascript interpreter because for now, Javascript on browsers is single-threaded. (Worker threads are coming, though — whoo hoo!) But in theory, the click could happen and get queued by a non-Javascript UI thread in the browser, then the ajax could complete and replace the element, and then the queued UI event gets processed and doesn't happen at all or doesn't bubble because the element no longer has a parent, having been removed. Whether that can actually happen will depend a lot on the browser implementation. If you're seeing it on any open source browsers, you might look at their source for queuing up UI events for processing by the interpreter. But that's a different matter than actually removing the element with code within the event handler as I have below.

Empirical results for the does-bubbling-continue aspect:

Tested Chrome 4 and Safari 4 (e.g., WebKit), Opera 10.51, Firefox 3.6, IE6, IE7, and IE8. IE was the only one that cancelled the event when you removed the element (and did so consistently across versions), none of the others did. Doesn't seem to matter whether you're using DOM0 handlers or more modern ones.

UPDATE:
On testing, IE9 and IE10 continue the event, so IE noncompliance with spec stops at IE8.

Test page using DOM0 handlers:

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

Test page using attachEvent/addEventListener handlers (via Prototype):

<!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>
可爱暴击 2024-09-06 20:31:09

自从我最初发布这个问题以来已经有一段时间了。尽管 TJCrowder 的回答内容非常丰富(Andy E 的回答也是如此),并且告诉我它应该有效,但我仍然发现了一个问题。我把它搁置了一段时间,但今天在另一个网络应用程序中再次遇到同样的问题时又重新审视了它。

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

根据 Mozilla 开发网络

当用户单击某个元素时,将引发单击事件。 click 事件将在 mousedown 和 mouseup 事件之后发生。

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

附加测试(请参阅 jsfiddle 上的 示例)显示,如果单击,则按住按钮并按下等待 DOM 元素发生变化,然后释放按钮,我们可以观察到(在 jquery 实时上下文中):

  • 'click' 事件触发
  • 第一个节点的 'mousedown' 事件触发
  • 更新的节点会触发“mouseup”事件编辑

:在 IE8 中测试。 IE8 为第一个节点触发 mousedown,为更新的节点触发 mouseup,但实际上确实触发“单击”,使用更新的节点作为事件源。

It's been quite some time since I originally posted this question. Although T.J.Crowder's answer was very informative (as was Andy E's), and told me it should work, I continued to see a problem. I put it aside for some time, but revisited it today when I encountered the same issue again in another web application.

I played around with it for a while, and I came to realize how to duplicate the problem every-time (at least in FF3.6 and Chrome 8). The problem wasn't that the event bubble was getting cancelled, or lost when the DOM element was removed. Instead, the problem is that if the element is changed between mousedown and mouseup, the 'click' does not fire.

Per the Mozilla Development Network:

The click event is raised when the user clicks on an element. The click event will occur after the mousedown and mouseup events.

So, when you have a DOM element that is changing at all, you can encounter this problem. And I erroneously believed the event bubble was being lost. It just happens that if you have a frequently updating element, you see it more often (which is my case) and are less likely to pass it off as a fluke.

Additional testing (see the example on jsfiddle) shows that if one clicks, holds the button down and waits for the DOM element to change, and then releases the button, we can observe (in the jquery live context):

  • The 'click' event does not fire
  • The 'mousedown' event fires for the first node
  • The 'mouseup' event fires for the updated node

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.

ぃ双果 2024-09-06 20:31:09

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

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


T.J. Crowder's comment made me decide to knock up a quick example. I was right on both counts, it does bubble and you can still get a reference to the removed node using event.target.

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


As T.J. discovered, that is not the case in IE. But the DOM Level 2 Events specification does define it as correct behavior [emphasis mine]:.

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

Yes, it should continue to propagate. Events have no real attachment to the event they fired on, except for the target property. When you remove the element, the internal code propagating the event should not have any "awareness" that the original element has gone from the visible document.

As an aside, using removeChild will not delete an element right away, it just detaches it from the document tree. An element should only be deleted/garbage collected when there are no references to it. Therefore, it's possible that the element could still be referred to via the event.target property and even re-inserted before being garbage collected. I haven't tried it though, so it's just speculation.


T.J. Crowder's comment made me decide to knock up a quick example. I was right on both counts, it does bubble and you can still get a reference to the removed node using event.target.

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


As T.J. discovered, that is not the case in IE. But the DOM Level 2 Events specification does define it as correct behavior [emphasis mine]:.

Events which are designated as bubbling will initially proceed with the same event flow as non-bubbling events. The event is dispatched to its target EventTarget and any event listeners found there are triggered. Bubbling events will then trigger any additional event listeners found by following the EventTarget's parent chain upward, checking for any event listeners registered on each successive EventTarget. This upward propagation will continue up to and including the Document. EventListeners registered as capturers will not be triggered during this phase. The chain of EventTargets from the event target to the top of the tree is determined before the initial dispatch of the event. If modifications occur to the tree during event processing, event flow will proceed based on the initial state of the tree.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文