JavaScriptでの新しい要素へのイベントハンドラの追加

ここにリスト ul とボタンがあるとします:

html<ul>
  <li>item</li>
  <li>item</li>
  <li>item</li>
  <li>item</li>
  <li>item</li>
</ul>

<button>add</button>

対応する変数に要素を取得しましょう:

javascriptlet button = document.querySelector('button');
let list   = document.querySelector('ul');
let items  = list.querySelectorAll('li');

任意の li をクリックすると、 その末尾に感嘆符が追加されるようにしましょう:

javascriptfor (let item of items) {
  item.addEventListener('click', function() {
    this.textContent = this.textContent + '!';
  });
}

次に、ボタンを押すとリストの末尾に新しい li が追加されるようにしましょう:

javascriptbutton.addEventListener('click', function() {
  let item = document.createElement('li');
  item.textContent = 'item';
  list.appendChild(item);
});

しかし、ここで問題が発生します: 新しく追加された li をクリックしても、末尾に感嘆符が追加されません。 なぜなら、クリックハンドラは最初から存在していた li にのみ追加されており、新しい要素には追加されていない からです。

この問題を修正するために、新しい li に クリックハンドラを追加しましょう:

javascriptbutton.addEventListener('click', function() {
  let item = document.createElement('li');
  item.textContent = 'item';
  
  item.addEventListener('click', function() { // クリックハンドラ
    this.textContent = this.textContent + '!';
  });
  
  list.appendChild(item);
});

しかし、これではハンドラ関数のコードが 2か所(最初から存在する li 用と新しい要素用)で 重複しています。これを修正するため、コードを別の関数に 抽出しましょう:

javascriptfunction handler() {
  this.textContent = this.textContent + '!';
}

この関数を使用して、コードの重複を回避しましょう:

javascriptfor (let item of items) {
  item.addEventListener('click', handler);
}

button.addEventListener('click', function() {
  let item = document.createElement('li');
  item.textContent = 'item';
  
  item.addEventListener('click', handler);
  
  list.appendChild(item);
});

問題は概ね解決し、ハンドラ関数のコードの重複は避けられました。 しかし、イベントハンドラの追加は依然として2か所 (既存の li のためのループ内と、ボタンクリック時)で 行う必要があります。次のレッスンでは、この不便さを 解消する方法について検討します。