Удаление элемента через parentNode.removeChild вызывает исключение DOM 8

У меня есть код, который примерно так (я удалил некоторые части, так как они не имеют значения):

Library.focus = function(event) {
    var element, paragraph;

    element = event.srcElement;

    paragraph = document.createElement("p");
    paragraph.innerText = element.innerText;

    element.parentNode.insertBefore(paragraph, element);        // Line #1
    element.parentNode.removeChild(element);                    // Line #2
};

Проблема, с которой я столкнулся, заключается в том, что последний вызов, который я пронумеровал как строка №2, выдает это:

Uncaught Error: NOT_FOUND_ERR: DOM Exception 8

Обратите внимание, что предыдущая строка #1 работает нормально и вставляет узел абзаца перед ним. Элемент является существующим элементом, и существуют также element.parentNode и element.parentNode.removeChild.

Я считаю это нелогичным, так как элемент по определению является дочерним по отношению к parentNode. Может быть, Stackru сможет помочь мне, здесь?

3 ответа

Решение

Из MDN документов:

Если потомок на самом деле не является потомком узла элемента, метод выдает исключение. Это также произойдет, если дочерний элемент был на самом деле дочерним элементом элемента во время вызова, но был удален обработчиком событий, вызванным в ходе попытки удалить элемент (например, размытие.)

Я могу воспроизвести эту ошибку в jsfiddle

По сути, вы фокусируете элемент, который вызывает удаление, вызывает размытие, которое перемещает элемент, что делает элемент больше не родительским.

Если вы пытаетесь изменить в обработчике onblur тот же самый узел, который вы пытаетесь удалить в другом обработчике (например, onfocus, keydown и т. Д.), То первый вызов removeChild запустит обработчик onblur перед тем, как фактически удалить узел. Если обработчик onblur затем модифицирует узел так, чтобы его родитель изменился, управление вернется из обработчика onblur к removeChild вызовите ваш обработчик onfocus, который затем попытается удалить узел и завершится ошибкой с исключением, которое вы описали.

Любая сумма проверки на наличие ребенка перед звонком removeChild в вашем обработчике onfocus будет бесплодным, так как эти проверки произойдут до того, как обработчик onblur будет запущен.

Помимо реорганизации обработки событий и модификаций узлов, вам лучше всего просто обработать исключение в блоке try catch.

Следующий код покажет, как работает обработчик события onblur removeChild в onfocus фактически удаляет ребенка. Это также на jsfiddle

HTML

<div id="iParent">
    <input id="i">
</div>

JS

var input = document.querySelector("#i"),
inputParent = document.querySelector('#iParent');

input.onblur = function() {
    console.log('onblur : input ' + (input.parentNode ? 'has' : 'doesnt have') 
            + ' a parent BEFORE delete');
    try {
        inputParent.removeChild(input);
    } catch (err) {
        console.log('onblur : removeChild failed, input ' 
                + (input.parentNode ? 'has' : 'doesnt have') + ' a parent');
        return false;
    }
    console.log('onblur : child removed');
};

input.onfocus = function() {
    console.log('onfocus : input ' + (input.parentNode ? 'has' : 'doesnt have') 
            + ' a parent BEFORE delete');
    try {
        inputParent.removeChild(input);
    } catch (err) {
        console.log('onfocus : removeChild failed, input ' 
                + (input.parentNode ? 'has' : 'doesnt have') + ' a parent');
        return false;
    }
    console.log('onfocus : child removed');
};​

Вывод на консоль после фокусировки на поле ввода будет

onfocus : input has a parent BEFORE delete
onblur : input has a parent BEFORE delete
onblur : child removed
onfocus : removeChild failed, input doesnt have a parent 

Не передавайте событие как параметр для функции Library.focus. это будет работать.

Library.focus = function() {
    var element, paragraph;

    element = event.srcElement;

    paragraph = document.createElement("p");
    paragraph.innerText = element.innerText;

    element.parentNode.insertBefore(paragraph, element);        // Line #1
    element.parentNode.removeChild(element);                    // Line #2
};
Другие вопросы по тегам