Можно ли добавить к innerHTML, не уничтожая прослушивателей событий потомков?

В следующем примере кода я присоединяю обработчик событий onclick к диапазону, содержащему текст «foo». Обработчик — это анонимная функция, которая выводит alert().

Однако, если я назначу родительскому узлу innerHTML, этот обработчик событий onclick будет уничтожен - нажатие «foo» не приведет к появлению окна предупреждения.

Это поправимо?

<html>
 <head>
 <script type="text/javascript">

  function start () {
    myspan = document.getElementById("myspan");
    myspan.onclick = function() { alert ("hi"); };

    mydiv = document.getElementById("mydiv");
    mydiv.innerHTML += "bar";
  }

 </script>
 </head>

 <body onload="start()">
   <div id="mydiv" style="border: solid red 2px">
     <span id="myspan">foo</span>
   </div>
 </body>

</html>

person mike    schedule 27.02.2009    source источник
comment
Этот вопрос связан с проблемой «добавление новых полей в форму стирает пользовательский ввод». Выбранный ответ очень хорошо решает обе эти проблемы.   -  person MattT    schedule 24.07.2014
comment
Для решения этой проблемы можно использовать делегирование событий.   -  person dasfdsa    schedule 28.09.2019


Ответы (13)


К сожалению, присвоение innerHTML приводит к уничтожению всех дочерних элементов, даже если вы пытаетесь добавить. Если вы хотите сохранить дочерние узлы (и их обработчики событий), вам необходимо использовать DOM-функции:

function start() {
    var myspan = document.getElementById("myspan");
    myspan.onclick = function() { alert ("hi"); };

    var mydiv = document.getElementById("mydiv");
    mydiv.appendChild(document.createTextNode("bar"));
}

Редактировать: решение Боба из комментариев. Опубликуйте свой ответ, Боб! Получите кредит за это. :-)

function start() {
    var myspan = document.getElementById("myspan");
    myspan.onclick = function() { alert ("hi"); };

    var mydiv = document.getElementById("mydiv");
    var newcontent = document.createElement('div');
    newcontent.innerHTML = "bar";

    while (newcontent.firstChild) {
        mydiv.appendChild(newcontent.firstChild);
    }
}
person Ben Blank    schedule 27.02.2009
comment
Есть ли замена, которая может добавить произвольный фрагмент HTML? - person mike; 27.02.2009
comment
новое содержимое = document.createElement('div'); newcontent.innerHTML= произвольный_блоб; в то время как (newcontent.firstChild) mydiv.appendChild (newcontent.firstChild); - person bobince; 27.02.2009
comment
Здорово, Боб! Если вы опубликуете это как хорошо отформатированный ответ, я выберу его. - person mike; 27.02.2009
comment
@bobince — я обновил свой ответ с помощью вашей техники, так как вы еще не опубликовали ее сами. Если вы создадите свой собственный ответ, откатите мой назад. :-) - person Ben Blank; 27.02.2009
comment
@Ben: вам не нужен вызов newcontent.removeChild(). Первоначальный комментарий Боба был правильным. - person Crescent Fresh; 27.02.2009
comment
@crescentfresh — если вы не удалите дочерний узел из нового контента, while (newcontent.firstChild) зациклится навсегда. Попробуй. - person Ben Blank; 28.02.2009
comment
Хм… Беру свои слова назад. Он не должен зацикливаться — я понятия не имел, что пошло не так, когда впервые протестировал его. :-/ - person Ben Blank; 28.02.2009
comment
О, и последнее, вам понадобятся «var myspan», «var newcontent» и т. д., чтобы избежать случайного сброса глобальных переменных. - person bobince; 28.02.2009
comment
Хе. Становится очевидным, что моя склонность к быстрому и грязному коду примера/прототипа не помогает мне в SO. :-) - person Ben Blank; 28.02.2009
comment
firstChild у меня не сработало, как ожидалось, но firstElementChild сработало. - person Omar; 18.06.2020

Использование .insertAdjacentHTML() сохраняет прослушиватели событий и поддерживается всеми основными браузерами. Это простая однострочная замена .innerHTML.

var html_to_insert = "<p>New paragraph</p>";

// with .innerHTML, destroys event listeners
document.getElementById('mydiv').innerHTML += html_to_insert;

// with .insertAdjacentHTML, preserves event listeners
document.getElementById('mydiv').insertAdjacentHTML('beforeend', html_to_insert);

Аргумент 'beforeend' указывает, куда в элемент вставить содержимое HTML. Варианты: 'beforebegin', 'afterbegin', 'beforeend' и 'afterend'. Их соответствующие местоположения:

<!-- beforebegin -->
<div id="mydiv">
  <!-- afterbegin -->
  <p>Existing content in #mydiv</p>
  <!-- beforeend -->
</div>
<!-- afterend -->
person Josh de Leeuw    schedule 07.12.2016
comment
Вы спасли мой день! - person Tural Asgar; 06.11.2019
comment
Лучшее решение. Спасибо! - person Dawoodjee; 24.11.2019
comment
будьте осторожны Chrome/Edge жалуется на чувствительность к регистру: т.е. afterbegin не принимается afterBegin принимается! - person AK_; 20.01.2021
comment
Это должен быть принятый ответ. - person Lewis Morris; 04.05.2021

Сейчас 2012 год, и в jQuery есть функции добавления и добавления, которые делают именно это, добавляют контент, не влияя на текущий контент. Очень полезно.

person rocketsarefast    schedule 20.01.2012
comment
.insertAdjacentHTML существует с IE4 - person Esailija; 23.07.2012
comment
@Tynach да, это их сайт JavaScript, который документирует это лучше всего: developer.mozilla.org/en-US/docs/Web/API/Element/ - person Jordon Bedwell; 25.11.2016
comment
@JordonBedwell, честно говоря, я понятия не имею, почему я сказал то, что сделал раньше. Ты прав на 100%. Мне кажется, что в то время я мельком просмотрел его и не смог найти, но... Честно говоря, у меня нет оправдания. Esailija даже ссылается на эту страницу. - person Tynach; 09.09.2017
comment
ОП не говорит о jQuery - person ; 15.10.2019

В качестве небольшого (но связанного) отступления, если вы используете библиотеку javascript, такую ​​​​как jquery (v1.3), для выполнения манипуляций с домом, вы можете использовать живые события, с помощью которых вы настраиваете обработчик, например:

 $("#myspan").live("click", function(){
  alert('hi');
});

и он будет применяться к этому селектору всегда во время любых манипуляций с jquery. О живых событиях см.: docs.jquery.com/events/live о манипулировании jquery см.: docs.jquery.com/manipulation

person Cargowire    schedule 28.02.2009
comment
ОП не говорит о jQuery - person ; 15.10.2019

Я создал свою разметку для вставки в виде строки, так как в ней меньше кода и ее легче читать, чем работать с причудливыми вещами dom.

Затем я сделал innerHTML временного элемента, чтобы я мог взять единственный дочерний элемент этого элемента и прикрепить его к телу.

var html = '<div>';
html += 'Hello div!';
html += '</div>';

var tempElement = document.createElement('div');
tempElement.innerHTML = html;
document.getElementsByTagName('body')[0].appendChild(tempElement.firstChild);
person Eneroth3    schedule 04.07.2012

Да, это возможно, если вы связываете события с помощью атрибута тега onclick="sayHi()" непосредственно в шаблоне, аналогичном вашему <body onload="start()"> - этот подход аналогичен фреймворкам angular/vue/react/etc. Вы также можете использовать <template> для работы с динамическими ' html, как здесь. Это не строгий ненавязчивый js, однако он приемлем для небольших проектов.

function start() {
  mydiv.innerHTML += "bar";
}

function sayHi() {
  alert("hi");
}
<body onload="start()">
  <div id="mydiv" style="border: solid red 2px">
    <span id="myspan" onclick="sayHi()">foo</span>
  </div>
</body>

person Kamil Kiełczewski    schedule 02.05.2019

Есть и другая альтернатива: использование setAttribute вместо добавления прослушивателя событий. Нравится:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Demo innerHTML and event listeners</title>
<style>
    div {
        border: 1px solid black;
        padding: 10px;
    }
</style>
</head>
<body>
    <div>
        <span>Click here.</span>
    </div>
    <script>
        document.querySelector('span').setAttribute("onclick","alert('Hi.')");
        document.querySelector('div').innerHTML += ' Added text.';
    </script>
</body>
</html>

person Frank Conijn    schedule 27.06.2018

Потеря обработчиков событий, IMO, является ошибкой в ​​том, как Javascript обрабатывает DOM. Чтобы избежать такого поведения, вы можете добавить следующее:

function start () {
  myspan = document.getElementById("myspan");
  myspan.onclick = function() { alert ("hi"); };

  mydiv = document.getElementById("mydiv");
  clickHandler = mydiv.onclick;  // add
  mydiv.innerHTML += "bar";
  mydiv.onclick = clickHandler;  // add
}
person Yes - that Jake.    schedule 27.02.2009
comment
Я не считаю это ошибкой. Замена элемента означает его полную замену. Он не должен наследовать то, что было раньше. - person Diodeus - James MacFarlane; 27.02.2009
comment
Но я не хочу заменять элемент; Я просто хочу добавить новые к родителю. - person mike; 27.02.2009
comment
Если бы вы заменяли элемент, я бы согласился, @Diodeus. Обработчик событий имеет не .innerHTML, а родитель .innerHtml. - person Yes - that Jake.; 27.02.2009
comment
Владелец innerHTML не теряет обработчиков при изменении его innerHTML, только что-либо в его содержимом. И JavaScript не знает, что вы только добавляете новых дочерних элементов, поскольку «x.innerHTML+= y» — это всего лишь синтаксический сахар для строковой операции «x.innerHTML= x.innerHTML+y». - person bobince; 27.02.2009
comment
Вам не нужно повторно прикреплять mydiv.onclick. Только onclick внутреннего диапазона переопределяется innerHTML +=. - person Crescent Fresh; 27.02.2009

Вы можете сделать это следующим образом:

var anchors = document.getElementsByTagName('a'); 
var index_a = 0;
var uls = document.getElementsByTagName('UL'); 
window.onload=function()          {alert(anchors.length);};
for(var i=0 ; i<uls.length;  i++)
{
    lis = uls[i].getElementsByTagName('LI');
    for(var j=0 ;j<lis.length;j++)
    {
        var first = lis[j].innerHTML; 
        string = "<img src=\"http://g.etfv.co/" +  anchors[index_a++] + 
            "\"  width=\"32\" 
        height=\"32\" />   " + first;
        lis[j].innerHTML = string;
    }
}
person Rishabh gupta    schedule 27.03.2014

something.innerHTML += 'добавляйте все, что хотите';

это сработало для меня. Я добавил кнопку для ввода текста, используя это решение

person KawaiKx    schedule 12.07.2018
comment
Это был самый простой метод, с которым я сталкивался, спасибо! - person demo7up; 19.09.2019
comment
Он уничтожает все события. - person Tural Asgar; 06.11.2019
comment
как это действительно решение? он уничтожает все события!! - person Danish; 20.11.2019

Самый простой способ - использовать массив и вставлять в него элементы, а затем динамически вставлять последующие значения массива в массив. Вот мой код:

var namesArray = [];

function myclick(){
    var readhere = prompt ("Insert value");
    namesArray.push(readhere);
    document.getElementById('demo').innerHTML= namesArray;
}
person Anoop Anson    schedule 24.10.2018

Для любого массива объектов с заголовком и данными.jsfiddle

https://jsfiddle.net/AmrendraKumar/9ac75Lg0/2/

<table id="myTable" border='1|1'></table>

<script>
  const userObjectArray = [{
    name: "Ajay",
    age: 27,
    height: 5.10,
    address: "Bangalore"
  }, {
    name: "Vijay",
    age: 24,
    height: 5.10,
    address: "Bangalore"
  }, {
    name: "Dinesh",
    age: 27,
    height: 5.10,
    address: "Bangalore"
  }];
  const headers = Object.keys(userObjectArray[0]);
  var tr1 = document.createElement('tr');
  var htmlHeaderStr = '';
  for (let i = 0; i < headers.length; i++) {
    htmlHeaderStr += "<th>" + headers[i] + "</th>"
  }
  tr1.innerHTML = htmlHeaderStr;
  document.getElementById('myTable').appendChild(tr1);

  for (var j = 0; j < userObjectArray.length; j++) {
    var tr = document.createElement('tr');
    var htmlDataString = '';
    for (var k = 0; k < headers.length; k++) {
      htmlDataString += "<td>" + userObjectArray[j][headers[k]] + "</td>"
    }
    tr.innerHTML = htmlDataString;
    document.getElementById('myTable').appendChild(tr);
  }

</script>
person AKS    schedule 21.07.2019

Я ленивый программист. Я не использую DOM, потому что это похоже на дополнительную печать. Для меня чем меньше кода, тем лучше. Вот как я бы добавил «bar», не заменяя «foo»:

function start(){
var innermyspan = document.getElementById("myspan").innerHTML;
document.getElementById("myspan").innerHTML=innermyspan+"bar";
}
person lucious    schedule 09.07.2010
comment
Этот вопрос спрашивает, как это сделать без потери обработчиков событий, поэтому ваш ответ не имеет значения, и, кстати, это то, что делает +=, что намного короче - person wlf; 23.11.2012
comment
Забавно, что в этом совершенно неверном ответе, в котором в качестве оправдания приводится отказ от лишнего ввода, около половины кода является избыточным. - person JLRishe; 20.10.2017