普通文組 2.5

為什麼使用innerHTML對元素新增內容後,該元素「子代」的事件監聽器失效?

問題描述

  1. <button id="alert">alert('hello world')</button>加上事件監聽器,使其被點擊後會出現 alert 訊息
  2. 透過innerHTML<div id="target">新增一個<span>標籤(不對<button id="alert">進行任何處理),但新增完畢後,點擊<button id="alert">無法出現 alert 訊息了
  3. 示範用原始碼如下:

See the Pen innerHTML destroying descendants' event listeners by Charlie (@Charlie7779) on CodePen.

原因

  • innerHTML實際上是「移除原本的內容後,以新的內容取代之」
  • 就算是新增,也會觸發上述的「移除 ─ 新增」機制,綁定好的事件監聽器的子代元素也會一起被移除

MDN: Setting the value of innerHTML removes all of the element’s descendants and replaces them with nodes constructed by parsing the HTML given in the string htmlString.

  • 在示範用的程式碼中,點擊<button id="replace"><div id="target">新增一個<span>後,原先綁定了事件監聽器的<button id="alert">已被移除,並被一個新的<button id="alert">取代
  • 新的<button id="alert">沒有任何事件監聽器,故點擊後不會出現 alert 訊息

解決方式

createElement()搭配 appendChild()

See the Pen appendChild by Charlie (@Charlie7779) on CodePen.

  • 透過let span = document.createElement("span")建立一個<span>元素
  • 使用textContent設定span的文字內容
  • 執行target.appendChild(span)span加為target的子代元素

MDN: The Node.appendChild() method adds a node to the end of the list of children of a specified parent node.

insertAdjacentHTML()

See the Pen insertAdjacentHTML() by Charlie (@Charlie7779) on CodePen.

關於element.insertAdjacentHTML(position, text)

  • position參數以下四選一:
    • beforebegin:在element前插入text
    • afterbegin:在element內部、第一個 childNode 前插入text
    • beforeend:在element內部、最後的 childNode 後插入text
    • afterend:在element後插入text
  • text:會被轉換為 HTML 節點,可使用 template literal

See the Pen insertAdjacentHTML() -- position by Charlie (@Charlie7779) on CodePen.

參考文件