Las fugas de memoria son un tema importante que afecta al rendimiento de un sitio web y sus usuarios al utilizarlo, y se basa en cómo manejas los eventos y como está construído el código de tu página.
¿Qué es una fuga de memoria?
Al utilizar variables y objetos de nuestro código, estos se cargan en memoria y se utilizan cuando es necesario. Sin embargo, muchas veces dejan de ser necesarios y lo ideal sería que se eliminaran de memoria para no ocuparla sin necesidad, por ejemplo, con el Garbage Collector de Javascript, un sistema automático que busca los objetos o variables que ya no se necesitan para eliminarlos de memoria.
La fuga de memoria se produce cuando resulta imposible para el Garbage Collector eliminar de memoria un dato u objeto porque se encuentra referenciado y asume que puede volver a ser utilizado. Esto hace que la página continue cargando y cargando datos en memoria, consumiendo más y más memoria.
Las fugas en .addEventListener()
El problema de las fugas de memoria en los addEventListener()
se produce cuando se añade un listener a un elemento del DOM y luego, ese elemento del DOM se elimina de la página sin haber eliminado el listener con removeEventListener()
.
Lo que ocurre es que el listener sigue referenciado al elemento del DOM y por lo tanto es imposible para el Garbage Collector eliminarlo de memoria. Esto se puede complicar si estamos en un bucle o si se añaden y eliminan elementos al DOM continuamente, ya que se estarían acumulando listeners que no se pueden eliminar, consumiendo cada vez más memoria.
El método .removeEventListener()
El método .removeEventListener()
, como su propio nombre indica, sirve para eliminar un listener que se ha añadido previamente al elemento con .addEventListener()
:
Método | Descripción |
---|---|
.removeEventListener( event, func) | Elimina la funcionalidad func asociada al evento event . |
Veamos un ejemplo, donde añadimos dos listener click
que hacen cada uno una función: action
y toggle
. Luego, retiramos uno de ellos (action) con .removeEventListener()
. En este ejemplo sólo debería actuar la funcionalidad toggle
:
const button = document.querySelector("button");
const action = () => alert("Hello!");
const toggle = () => button.classList.toggle("red");
button.addEventListener("click", action); // Añade listener de saludar
button.addEventListener("click", toggle); // Añade listener de color
button.removeEventListener("click", action); // Elimina listener de saludar
.red { background: red }
<button>Saludar</button>
Observa que hemos utilizado la constante para referenciar a la función que queremos añadir en el listener o retirarla. Tanto en el addEventListener()
como en el removeEventListener()
se hace referencia a la misma función: a action
.
Un error muy típico es indicar una función diferente que hace lo mismo que la primera:
button.addEventListener("click", function() { alert("Hello!"); });
button.removeEventListener("click", function() { alert("Hello!"); });
Este ejemplo no funcionaría correctamente, porque la primera función es diferente a la segunda, aunque hagan lo mismo. El método removeEventListener()
se espera que se le pase exactamente la misma función.
La opción once
de addEventListener()
Utilizando la opción once: true
en las opciones de un addEventListener()
podemos crear listeners de un sólo uso, que trabajarían de forma similar a un addEventListener()
que cuando se ejecuta una vez, se elimina con un removeEventListener()
:
<button>Saludar sólo una vez</button>
<script>
const button = document.querySelector("button");
button.addEventListener("click", () => {
alert("¡Hola desde LenguajeJS!");
}, { once: true });
</script>
Si pulsamos en el botón, comprobaremos que se ejecuta la función y se produce el alert
. Sin embargo, si volvemos a pulsar el botón, no se volverá a ejecutar. El once
determina que el listener funcionará sólo la primera vez, y luego se eliminará. Es algo interesante si tenemos funcionalidades que no queremos que ocurran más de una vez.
Errores comunes
Existen situaciones donde se producen fugas de memoria comunmente:
1️⃣ Eventos globales. Cuando haces un
addEventListener()
enwindow
,document
oglobalThis
. Este listener seguirá funcionando incluso cuando cambias de página en una SPA (Single Page Application).2️⃣ Si usas un
addEventListener()
que no se elimina con elremoveEventListener()
.
Por estas razones, por cada addEventListener()
que utilices, piensa que debería usarse un removeEventListener()
en el caso de que ya no se utilice o se elimine del DOM.