Skip to content
This page has been auto-translated and may contain errors.View in English

Eventos

docs.scrimba.com

Una página que nunca reacciona es solo un documento. En el momento en que quieres que algo suceda cuando una persona hace clic en un botón, escribe en un cuadro de búsqueda o envía un formulario, necesitas una forma de decir "cuando esto sucede, ejecuta este código". Eso es lo que son los eventos. Este capítulo trata sobre escuchar las cosas que hace un usuario y responder a ellas.

Escuchar un evento

Un evento es algo que sucede en la página: un clic, una pulsación de tecla, un envío de formulario. Para responder a uno, eliges un elemento, nombras el evento que te importa y pasas una función para ejecutar cuando sucede.

Esa función se llama controlador. El método que la conecta es addEventListener:

js
const button = document.querySelector(".buy-button");

button.addEventListener("click", () => {
  console.log("Alguien hizo clic en comprar");
});

Léelo de izquierda a derecha: en este botón, escucha "click", y cuando suceda, ejecuta esta función. El controlador se ejecuta cada vez que el evento sucede, no una sola vez. Haz clic tres veces y se ejecuta tres veces.

Un evento es una señal que el navegador dispara cuando algo sucede: el usuario hace clic, escribe, envía un formulario o la página termina de cargarse. Respondes registrando un controlador con addEventListener, que toma el nombre del evento como una cadena y una función para llamar cada vez que se dispara.

js
const button = document.querySelector(".buy-button");

const handleBuy = () => {
  console.log("Alguien hizo clic en comprar");
};

button.addEventListener("click", handleBuy);

Nota que handleBuy se pasa sin paréntesis. Le estás pasando la función en sí al navegador para que la llame después, no la estás llamando ahora. Escribir addEventListener("click", handleBuy()) la llamaría inmediatamente y registraría lo que devolviera, que es un error común al principio.

Un evento es un mensaje que el navegador envía a cualquier código que haya registrado interés en él. Te registras con addEventListener(type, handler): el tipo es el nombre del evento como una cadena, el controlador es una llamada de retorno que el navegador invoca cada vez que el evento se dispara en ese elemento.

js
const button = document.querySelector(".buy-button");

const handleBuy = () => {
  console.log("Alguien hizo clic en comprar");
};

button.addEventListener("click", handleBuy);

Dos cosas importan aquí. Primero, pasas handleBuy, el valor de la función, no handleBuy(), el resultado de llamarla. Segundo, addEventListener es aditivo: llámalo dos veces con dos funciones y ambas se ejecutan, por lo que los escuchadores se apilan en lugar de sobrescribirse. Esa es la razón práctica por la que existe en lugar de asignar button.onclick, que contiene un único controlador y sobrescribe cualquier anterior. Mantener una referencia nombrada al controlador también importa después, cuando quieras eliminarlo.

Los controladores son funciones ordinarias, y los elementos a los que los adjuntas provienen del DOM. Si puedes seleccionar un elemento y escribir una función, puedes responder a lo que el usuario hace con él.

JunoEscuchar un evento Para responder a un usuario, elige un elemento, nombra el evento como una cadena como "click", y dale a addEventListener una función para ejecutar. Esa función es el controlador, y se ejecuta cada vez que el evento sucede, no una sola vez. Este método es lo que está detrás de casi todo lo que una página hace en respuesta a ti.
JunoEscuchar un eventoaddEventListener toma un nombre de evento y una función, y llama a esa función cada vez que se dispara el evento. Pasa la función por nombre sin paréntesis: handleBuy, no handleBuy(). Añadir los paréntesis la llama de inmediato en lugar de en el clic, y eso engaña a casi todos una vez.
JunoEscuchar un eventoaddEventListener registra una llamada de retorno que el navegador dispara en cada evento coincidente, y se apila en lugar de sobrescribirse, por lo que la prefieres sobre onclick. Pasa el valor de la función, no el resultado de su llamada, por lo que handleBuy y nunca handleBuy(). Mantén también una referencia nombrada, porque la necesitarás cuando sea hora de eliminar el escuchador.

El objeto de evento

Cuando el navegador ejecuta tu controlador, le pasa una pieza de información: un objeto de evento que describe lo que sucedió. Captúralo dando a tu controlador un parámetro, usualmente nombrado event:

js
const button = document.querySelector(".buy-button");

button.addEventListener("click", (event) => {
  console.log(event.target); // el elemento en el que se hizo clic
});

La parte más útil es event.target: el elemento en el que sucedió el evento. Si el clic fue en el botón, event.target es ese botón. No tienes que seleccionarlo de nuevo, el evento ya te dice de dónde vino.

Cada controlador recibe un argumento: el objeto de evento, que contiene detalles sobre lo que sucedió. Lo capturas nombrando un parámetro, convencionalmente event (o e):

js
const search = document.querySelector(".search-input");

search.addEventListener("input", (event) => {
  console.log(event.target.value); // lo que el usuario ha escrito hasta ahora
});

event.target es el elemento en el que se disparó el evento. En campos de formulario es la estrella del espectáculo, porque event.target.value te da el texto actual de la entrada. Ese emparejamiento, evento input más event.target.value, es cómo lees lo que alguien está escribiendo mientras lo escribe.

El navegador pasa a tu controlador un único argumento, el objeto de evento, una instantánea de lo que sucedió: qué elemento, qué tecla, la posición del ratón y métodos para controlar el comportamiento predeterminado del evento. Lo capturas con un parámetro, convencionalmente event.

js
const search = document.querySelector(".search-input");

search.addEventListener("input", (event) => {
  console.log(event.target.value); // texto actual en el campo
});

event.target es el elemento que originó el evento, que no siempre es el elemento al que adjuntaste el escuchador (ese es event.currentTarget, y la brecha entre ellos es lo que hace funcionar la delegación, abajo). En controles de formulario, event.target.value lee el valor actual como una cadena, por lo que el mismo .value que HTML mantiene como texto regresa como texto aquí. Busca un número y lo conviertes tú mismo con Number(event.target.value).

JunoEl objeto de evento Tu controlador obtiene un argumento, el objeto de evento, así que dale un parámetro nombrado event para capturarlo. La parte que alcanzarás con más frecuencia es event.target, el elemento en el que sucedió el evento. No necesitas volver a seleccionarlo, el evento ya sabe de dónde vino.
JunoEl objeto de evento Nombra un parámetro event y el navegador lo llena con detalles sobre lo que sucedió. event.target es el elemento en el que se disparó, y en una entrada, event.target.value es el texto actualmente en el campo. Empareja el evento input con event.target.value y puedes leer lo que alguien escribe mientras lo escribe.
JunoEl objeto de evento El objeto de evento es una instantánea de lo que sucedió, y event.target es donde comenzó, que no siempre es donde adjuntaste el escuchador; ese es event.currentTarget. En campos de formulario event.target.value regresa como una cadena, así que envuélvelo en Number(...) cuando necesites un número. Ten en mente la división entre target y currentTarget, porque la delegación depende de ella.

Eventos comunes

Un puñado de eventos cubre la mayoría de lo que construirás:

  • click: el usuario hace clic o toca algo.
  • input: el valor de un campo de texto cambia mientras escribe.
  • submit: se envía un formulario.
  • keydown: se presiona una tecla.

El complicado es submit. Por defecto, enviar un formulario recarga la página, lo que borra cualquier cosa que tu JavaScript estuviera haciendo. Para evitar eso, llama a event.preventDefault() dentro del controlador:

js
const form = document.querySelector("form");

form.addEventListener("submit", (event) => {
  event.preventDefault(); // evita que la página se recargue
  console.log("Formulario enviado, la página permanece en su lugar");
});

La mayoría de las páginas dependen de un pequeño conjunto de eventos: click para botones, input para campos de texto mientras cambian, submit para formularios, y keydown para atajos de teclado. Aprende estos cuatro y puedes construir la mayoría de interacciones.

El que atrapa a la gente es submit. El comportamiento predeterminado de un formulario es recargar la página o navegar lejos, lo que descarta lo que tu código estaba manejando. Llama a event.preventDefault() para mantener la página en su lugar y tomar control tú mismo:

js
const form = document.querySelector("form");

const handleSubmit = (event) => {
  event.preventDefault();
  const email = form.querySelector(".email-input").value;
  console.log(`Registrando ${email}`);
};

form.addEventListener("submit", handleSubmit);

Escucha submit en el formulario en sí, no click en el botón, para que los usuarios de teclado que presionen Enter también estén cubiertos.

Cuatro eventos llevan la mayoría de interacciones: click, input (se dispara en cada cambio de valor, a diferencia de change que espera al desenfoque), submit, y keydown. Únelos al elemento correcto y la mayoría de la UI sale de ellos.

El evento submit es donde preventDefault se gana su lugar. La acción predeterminada de un formulario es una navegación completa, que descarta tu estado en la página. Llamar a event.preventDefault() cancela eso para que manejes los datos tú mismo:

js
const form = document.querySelector("form");

const handleSubmit = (event) => {
  event.preventDefault();
  const email = form.querySelector(".email-input").value;
  console.log(`Registrando ${email}`);
  // enviarlo a un servidor es el trabajo del próximo capítulo
};

form.addEventListener("submit", handleSubmit);

Adjunta al submit del formulario, no al click del botón: submit se dispara para Enter, clic del botón y envío programático por igual, por lo que es el gancho correcto. Una vez que tengas el valor, enviarlo a algún lugar es donde entra el código async, que es el próximo capítulo.

Propagación y delegación

Cuando haces clic en un elemento, el evento no se detiene allí. Se propaga: se dispara en el elemento en el que se hizo clic, luego en su padre, luego en el padre de ese padre, hasta arriba. Eso es por qué un escuchador en un padre escucha clics en sus hijos.

Ese comportamiento impulsa delegación: adjunta un escuchador a un padre y usa event.target para encontrar qué hijo fue realmente en el que se hizo clic. En lugar de un escuchador por elemento, obtienes uno que cubre todos ellos.

js
const list = document.querySelector(".todo-list");

list.addEventListener("click", (event) => {
  if (event.target.matches(".delete")) {
    event.target.closest("li").remove();
  }
});

El beneficio: esto maneja elementos añadidos a la lista después. Un escuchador por elemento solo vincula elementos que existen ahora; un escuchador delegado en el padre atrapa clics en hijos que no existían cuando se ejecutó el código, porque el evento todavía se propaga hasta el padre que está escuchando.

Eliminar escuchadores

removeEventListener desacopla un controlador, pero solo si pasas la referencia exacta de la función que añadiste:

js
const button = document.querySelector(".buy-button");

button.addEventListener("click", handleBuy);
button.removeEventListener("click", handleBuy); // funciona: misma referencia

Por eso los controladores anónimos no pueden ser eliminados. Una función de flecha en línea como addEventListener("click", () => {...}) crea una función nueva cada vez, así que no tienes una referencia para pasar a removeEventListener, y el escuchador queda atrapado. Los controladores nombrados que planeas eliminar evitan eso. Los escuchadores que sobreviven a los elementos que tocan son una fuente real de fugas de memoria, donde objetos que la página ya no necesita no pueden ser recuperados porque un escuchador aún apunta a ellos.

Una nota más sobre rendimiento: añadir { passive: true } como tercer argumento en escuchadores de desplazamiento o tacto promete que no llamarás a preventDefault, lo que permite al navegador desplazarse sin esperar a tu controlador y mantiene el desplazamiento suave.

JunoEventos comunes Los eventos que usarás más son click, input, submit, y keydown. El que debes recordar es submit: un formulario recarga la página por defecto, así que llama a event.preventDefault() en el controlador para evitar eso y mantener el control. Piérdelo y tu página se actualiza bajo tus pies, lo cual es confuso la primera vez.
JunoEventos comunesclick, input, submit, y keydown cubren la mayoría de lo que construyes. Escucha submit en el formulario, no click en el botón, para que Enter funcione para usuarios de teclado. Y llama a event.preventDefault() en un controlador de envío, o la página se recarga y lleva tu estado con ella.
JunoEventos comunes Los eventos se propagan a los padres, así que un escuchador delegado más event.target cubre muchos hijos, incluyendo los añadidos después. removeEventListener necesita la misma referencia de función que añadiste, por lo que los controladores anónimos nunca pueden ser eliminados y pueden tener fugas de memoria. Y submit supera al click de un botón porque se dispara para Enter también; mantén event.preventDefault() ahí para evitar la recarga.

Por dónde van los eventos desde aquí

Los eventos son cómo una página escucha. Eliges un elemento, nombras lo que te importa, y ejecutas una función cuando sucede, leyendo los detalles del objeto de evento mientras avanzas. Casi cada característica interactiva está construida a partir de ese bucle.

El siguiente paso natural es hacer algo lento en un controlador, como enviar un formulario a un servidor o cargar datos frescos, sin congelar la página. Eso es lo que el código async es para eso, y es el próximo capítulo. Los controladores que se cuelan en datos estructurados, como una lista de elementos que mantienes en objetos, también dependen de lo que ya sabes.