只有我使用 innerHTML 添加的最后一个元素保留其事件处理程序,为什么?

Only the last element I added using innerHTML keeps its event handlers, why?

我正在尝试制作一个脚本,将可交互对象信息注入标记页面的列表中。每当我尝试在 div 上添加一个 onclick 事件时,它工作正常,但是每当我尝试在 for 循环中添加更多事件时,它都不会按我预期的方式工作。

我在网页调试器中使用断点查看了发生了什么,我发现问题是它似乎在添加到下一个 [= 之前​​删除了前一个 div 上的事件28=]。最后,唯一剩下的事件是循环退出后的最后一个div。

我想将这些事件保留在我的所有 div 上,而不仅仅是最后一个...这似乎是什么问题?

var objects = ['Tom', 'Sauna', 'Traum'];

for (var i = 0; i < objects.length; i++){
    document.getElementById('list').innerHTML += "<div class='item' id='"+ i +"'>" + objects[i] + "</div>";
    document.getElementById(i).addEventListener("mouseup", function() {
        Select(this);
    });
}

function Select(char) {
    console.log(char);
}
div.item {
    border: 1px solid black;
    padding: 4px;
    margin: 4px;
}
<div id="list"></div>

当您更改 innerHTML 时,浏览器重建元素的内容,丢弃所有附加的事件处理程序。改为使用 DOM 方法:

    for (let i = 0; i < objects.length; i++){
        var block = document.createElement('div');
        block.setAttribute('id', i);
        document.getElementById('list').appendChild( block );
        block.addEventListener("mouseup", function() {
            Select(this);
        });
    }

UPD:或者使用 insertAdjacentHTML 方法而不是重新定义 innerHTML:

document.getElementById('list').insertAdjacentHTML(
    'beforeend', "<div id='"+ i +"'>" + i + "</div>");

原因在于您追加的方式。 innerHtml += 有效覆盖列表中的现有内容。因此,您添加和绑定的任何元素都会消失,每次都会添加新项目。

有几种方法可以完成这项工作。

首先,您可以附加元素而不是分配 innerHtml。

const items = ['taco', 'apple', 'pork'];
const list = document.getElementById("list");

for (const item of items) {
  const el = document.createElement("div");
  el.addEventListener('click', (e) => console.log(`clicked ${item}`));
  el.innerText = item;
  
  list.appendChild(el);
}
<div id="list"></div>

由于我们是附加一个明确的元素而不是覆盖内容,所以这会起作用。

更好的方法是使用 delegation。我们将单个事件处理程序分配到列表中并监听任何点击。然后我们找出点击了哪个特定元素。

const items = ['taco', 'apple', 'pork'];
const list = document.getElementById("list");
const add = document.getElementById("add");

list.addEventListener('click', (e) => {
 const parent = e.target.closest("[data-item]");
 
 if (parent != null) {
   console.log(`clicked on ${parent.dataset['item']}`);
 }
});

for (const item of items) {
  list.innerHTML += `<div data-item="${item}">${item}</div>`;
}

add.addEventListener('click', () => {
  const item = `item ${Date.now()}`;
  list.innerHTML += `<div data-item="${item}">${item}</div>`;
})
<div id="list"></div>

<button id="add">add</button>

这里的神奇之处在于我们在父级上分配了一个事件处理程序,并使用 closest 来确定单击了哪个项目。为简单起见,我在这里使用 innerHTML,但出于 security 的原因,应该避免使用它。

在适当的时候使用的一个好模式是 event delegation。它允许遵循 Don't Repeat Yourself 原则,使代码维护变得相当容易,并可能使脚本 运行 显着加快。在您的情况下,它避免了元素负责修改其自身内容的陷阱。

例如:

const container = document.getElementById('container');
container.addEventListener("click", toggleColor); // Events bubble up to ancestors

function toggleColor(event) { // Listeners automatically can access triggering events
  const clickedThing = event.target; // Event object has useful properties
  if(clickedThing.classList.contains("click-me")){ // Ensures this click interests us
    clickedThing.classList.toggle("blue");
  }
}
.click-me{ margin: 1em 1.5em; padding 1em 1.5em; }
.blue{ color: blue; }
<div id="container">
  <div id="firstDiv" class="click-me">First Div</div>
  <div id="firstDiv" class="click-me">Second Div</div>
</div>