Если вы удаляете элемент DOM, продолжаются ли какие-либо события, которые начались с этого элемента? - PullRequest
18 голосов
/ 29 апреля 2010

Какое поведение мне следует ожидать, если я удалю элемент DOM, который использовался для запуска всплывающего события, или чей дочерний элемент запустил всплывающее сообщение - будет ли он продолжать всплывать, если элемент будет удален?

Например - допустим, у вас есть таблица, и вы хотите обнаруживать события щелчка в ячейках таблицы. Другая часть JS выполнила AJAX-запрос, который в конечном итоге полностью заменит таблицу после завершения запроса.

Что произойдет, если я нажму на таблицу и сразу же после ее замены произойдет успешное завершение запроса AJAX? Я спрашиваю, потому что я вижу некоторое поведение, когда события щелчка не кажутся пузырящимися - но это трудно скопировать.

Я наблюдаю за событием в родительском элементе таблицы (вместо того, чтобы прикреплять событие к каждому TD), и иногда кажется, что оно просто не достигает его.

EDIT : снова столкнулся с этой проблемой и, наконец, дошел до ее корня. Не было никакой проблемы с событиями вообще! См. Мой ответ ниже для деталей.

Ответы [ 3 ]

23 голосов
/ 29 апреля 2010

Опытным путем: Это зависит от того, какой браузер вы используете; IE отменяет событие, все остальное (насколько я могу судить) продолжает его. Смотрите тестовые страницы и обсуждение ниже.

Теоретически: Голова Энди Е услужливо обнаружил , что DOM2 говорит, что событие должно продолжаться , потому что пузыри должны основываться на начальное состояние дерева. Таким образом, поведение большинства является правильным, IE здесь сам по себе. Квелль сюрприз.

Но: Имеет ли это отношение к тому, что вы видите, это действительно другой вопрос. Вы отслеживаете клики по родительскому элементу таблицы, и вы подозреваете, что очень редко, когда вы щелкаете по таблице, возникает условие гонки с завершением Ajax, которое заменяет таблицу, и щелчок теряется. Это условие гонки не может существовать в интерпретаторе Javascript, поскольку на данный момент Javascript в браузерах является однопоточным. (Рабочие потоки идут, хотя & ndash; уууууууу!) Но теоретически, щелчок может произойти и попасть в очередь в потоке пользовательского интерфейса, не являющегося Javascript, в браузере, тогда ajax может завершить и заменить элемент, а затем пользовательский интерфейс в очереди. событие обрабатывается и не происходит вообще, либо не всплывает, потому что у элемента больше нет родителя, поскольку он был удален. Может ли это произойти, зависит от реализации браузера lot . Если вы видите это в любых браузерах с открытым исходным кодом, вы можете посмотреть на их источник для постановки в очередь событий пользовательского интерфейса для обработки интерпретатором. Но это совсем другое дело, чем удаление элемента с кодом в обработчике событий, как показано ниже.

Эмпирические результаты для аспекта "пузырится-продолжить":

Протестировано Chrome 4 и Safari 4 (например, WebKit), Opera 10.51, Firefox 3.6, IE6, IE7 и IE8. IE был единственным, кто отменил событие, когда вы удалили элемент (и сделал это последовательно в разных версиях), ни одна из остальных не сделала. Кажется, не имеет значения, используете ли вы обработчики DOM0 или более современные.

UPDATE: При тестировании 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>
5 голосов
/ 05 января 2011

Прошло довольно много времени с тех пор, как я первоначально разместил этот вопрос. Хотя ответ Т.Дж.Кроудера был очень информативным (как и у Энди Э.) и сказал мне, что должен работать, я продолжал видеть проблему. Я отложил его на какое-то время, но вернулся к нему сегодня, когда снова столкнулся с той же проблемой в другом веб-приложении.

Я поэкспериментировал с этим некоторое время и понял, как каждый раз дублировать проблему (по крайней мере, в FF3.6 и Chrome 8). Проблема была не в том, что пузырь событий отменялся или терялся при удалении элемента DOM. Вместо этого проблема заключается в том, что если элемент изменяется между mousedown и mouseup, «щелчок» не срабатывает.

За Сеть развития Mozilla :

Событие click возникает, когда пользователь нажимает на элемент. Событие щелчка произойдет после событий mousedown и mouseup.

Итак, когда у вас есть DOM-элемент, который вообще меняется, вы можете столкнуться с этой проблемой. И я ошибочно полагал, что пузырь событий теряется. Просто бывает, что если у вас есть часто обновляемый элемент, вы видите его чаще (как в моем случае) и с меньшей вероятностью выдаете его за случайность.

Дополнительное тестирование (см. Пример на jsfiddle) показывает, что если нажать один раз, удерживать кнопку нажатой, ждать изменения элемента DOM, а затем отпускать кнопку, которую мы можем наблюдать (в jquery живой контекст):

  • Событие 'click' делает не fire
  • Событие 'mousedown' запускается для первого узла
  • Событие mouseup запускается для обновленного узла

РЕДАКТИРОВАТЬ: протестировано в IE8. IE8 запускает mousedown для первого узла, mouseup для обновленного узла, но делает фактически запускает «щелчок», используя обновленный узел в качестве источника события.

5 голосов
/ 29 апреля 2010

Да, он должен продолжать распространяться. События не имеют реальной привязки к событию, с которым они сработали, за исключением свойства target. Когда вы удаляете элемент, внутренний код, распространяющий событие, не должен иметь никакой «осведомленности» о том, что исходный элемент вышел из видимого документа.

Кроме того, использование removeChild не удалит элемент сразу, он просто отсоединит его от дерева документа. Элемент следует удалять / собирать только тогда, когда на него нет ссылок. Следовательно, возможно, что на элемент все еще можно ссылаться через свойство event.target и даже повторно вставить его перед сборкой мусора. Я еще не пробовал, так что это просто предположение.

<ч /> T.J. Комментарий Краудера заставил меня принять решение привести быстрый пример. Я был прав по обоим пунктам, это пузырь, и вы все еще можете получить ссылку на удаленный узел, используя event.target.

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

<ч /> Как и Т.Дж. обнаружил, что это не так в IE. Но спецификация DOM Level 2 Events определяет ее как правильное поведение [выделено мной]:.

События, которые обозначены как всплывающие сообщения, первоначально будут проходить с тем же потоком событий, что и не всплывающие события. Событие отправляется целевому EventTarget, и все найденные там слушатели событий срабатывают. Затем всплывающие события будут запускать любые дополнительные прослушиватели событий, найденные путем следования родительской цепочки EventTarget вверх, проверяя наличие прослушивателей событий, зарегистрированных для каждого последующего EventTarget. Это восходящее распространение будет продолжаться вплоть до Документа. EventListener, зарегистрированные как средства захвата, не будут запущены на этом этапе. Цепочка EventTargets от цели события до вершины дерева определяется до начальной отправки события. Если в процессе обработки событий произойдут изменения в дереве, поток событий будет продолжаться на основе исходного состояния дерева.

...