Asignar manejadores a nuevos elementos en JavaScript

Supongamos que tenemos una lista ul y un botón:

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

<button>add</button>

Obtengamos nuestros elementos en las variables correspondientes:

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

Hagamos que al hacer clic en cualquier li se agregue un signo de exclamación al final:

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

Ahora hagamos que al hacer clic en el botón se agregue un nuevo li al final de la lista:

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

Sin embargo, obtendremos un problema: el clic en la nueva li agregada no resultará en la adición de un signo de exclamación al final. El hecho es que agregamos el manejador de clic solo a aquellas li que existían inicialmente, pero no a las nuevas.

Solucionemos el problema asignando a la nueva li un manejador de clic:

javascriptbutton.addEventListener('click', function() {
  let item = document.createElement('li');
  item.textContent = 'item';
  
  item.addEventListener('click', function() { // manejador de clic
    this.textContent = this.textContent + '!';
  });
  
  list.appendChild(item);
});

Sin embargo, ahora el código de la función manejadora se duplica en dos lugares: para las li existentes inicialmente y para las nuevas. Arreglemos esto extrayendo el código a una función separada:

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

Usemos nuestra función para evitar duplicar código:

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);
});

En general, el problema está resuelto y hemos evitado duplicar el código de la función manejadora. Sin embargo, asignar manejadores de eventos todavía tenemos que hacerlo en dos lugares: tanto en el bucle para las li existentes, como en el clic del botón. En la siguiente lección, analizaremos una forma de deshacernos de esta molestia.