DEV Community

Alfredo Bonilla
Alfredo Bonilla

Posted on • Originally published at alfredobonilla.dev

WeakRefs y Finalizers en JavaScript

WeakRefs y Finalizers es una nueva característica de JavaScript añadida como parte de ES2021, su funcionamiento es bastante interesante pero puede ser complejo y no debe utilizarse si no es estrictamente necesario, a continuación veremos un poco más sobre esto.


🏁 TL;DR

En JavaScript las referencias a objetos son respetadas por el recolector de basura (o garbage collector), es decir, si el algoritmo de recolección encuentra que un objeto es alcanzable desde un objeto raíz este no será recolectado, pero si el objeto es inalcanzable, este será recolectado.

Se puede decir que este proceso es una aproximación ya que el algoritmo no puede saber a ciencia cierta si un objeto es necesario o no.

Un objeto weakRef permite mantener una referencia débil a otro objeto sin prevenir que este objeto sea eliminado de la memoria por el recolector de basura y FinalizationRegistry nos permite hacer un callback cuando este objeto ha sido debidamente recolectado.


📦 Gestión de la memoria en JavaScript

El ciclo de vida de la memoria es muy parecido en todos los lenguajes de programación primero se reserva la memoria necesaria, se utiliza y luego se libera cuando ya no es necesaria.

JavaScript asigna memoria de manera automática cada vez que se declara un valor al igual que la liberación de memoria no utilizada se realiza automáticamente por medio del Recolector de Basura o Garbage Collector.

🔗 WeakRefs

Una weakRef es un objeto que tiene una referencia débil a un objeto llamado referente u objetivo, esta referencia permite que el objeto sea recolectado por el garbage collector.

Cabe mencionar que se debe tener cuidado al utilizar las referencias débiles, de hecho es bueno evitarlas de ser posible. También debemos saber que el comportamiento del garbage collector puede cambiar de motor a motor y podría ser diferente para diferentes versiones del motor de JavaScript.

En otras palabras, el momento y la manera en que la recolección sucede puede variar y eso puede afectar el resultado que esperamos, incluso podría suceder que el método deref nunca retorne undefined debido a que el recolector decidió nunca recolectar esa referencia.

📣 FinalizationRegistry

FinalizationRegistry es un objeto que permite ejecutar un callback cuando un objeto es recolectado. Si el objetivo de un FinalizationRegistry es también un elemento con una WeakRef entonces la referencia se eliminará al mismo tiempo que la ejecución del callback por lo que no será posible obtener la referencia del objeto dentro del callback.

⚙️ Ejemplo

Este es un ejemplo simple donde vamos a definir una referencia débil para un objeto que va a contener una animación generada a partir de otros elementos del DOM que se adjuntan segundo a segundo durante 5 segundos.

Una vez que el tiempo haya pasado, la referencia será recolectada y se disparará un callback indicando que el objeto fue recolectado.

class CircleAnimation {
  constructor(element) {
    // Define una referencia débil a un elemento del DOM.
    this.ref = new WeakRef(element)

    // Define un callback que se ejecutara
    // cuando el elemento de referencia débil sea recolectado.
    this.registry = new FinalizationRegistry((hadValue) =>
      console.log("Referencia recolectada: ", hadValue)
    )
    this.registry.register(element, "Elemento de animación")

    this.start()
  }

  start() {
    if (this.timer) {
      return
    }

    const addCircle = () => {
      // Obtiene el elemento de la referencia.
      const referencedElement = this.ref.deref()
      if (referencedElement) {
        const circle = document.createElement("div")
        circle.className = "circle"
        referencedElement.append(circle)
      } else {
        // El elemento ya no existe.
        console.log("El elemento ya no existe.")
        this.stop()
        this.ref = null
      }
    }

    addCircle()
    this.timer = setInterval(addCircle, 1000)
  }

  stop() {
    if (this.timer) {
      clearInterval(this.timer)
      this.timer = 0
    }
  }
}

const animation = new CircleAnimation(document.getElementById("app"))
animation.start()

setTimeout(() => {
  document.getElementById("app").remove()
}, 5000)
Enter fullscreen mode Exit fullscreen mode

💻 El ejemplo completo se encuentra aquí.

‼️ Para poder observar su funcionamiento correctamente es necesario recargar la página cada vez que se ejecuta la animación.

¿Se les ocurre algún uso para esta característica? Dejen sus comentarios y opiniones sobre el tema.

Puedes encontrar el artículo original en mi blog.

¡Hasta la próxima! 😉

Top comments (0)