Características "ocultas" del idioma.
Este artículo cubre un tema muy específico que la mayoría de los desarrolladores rara vez encuentran en la práctica (y es posible que ni siquiera sean conscientes de su existencia).
Recomendamos omitir este capítulo si acaba de comenzar a aprender JavaScript.
Recordando el concepto básico del principio de accesibilidad del capítulo sobre recolección de basura, podemos observar que se garantiza que el motor JavaScript mantendrá valores en la memoria que sean accesibles o estén en uso.
Por ejemplo:
// la variable de usuario tiene una fuerte referencia al objeto dejar usuario = { nombre: "Juan" }; // sobrescribamos el valor de la variable de usuario usuario = nulo; // la referencia se pierde y el objeto se borrará de la memoria
O un código similar, pero un poco más complicado, con dos referencias sólidas:
// la variable de usuario tiene una fuerte referencia al objeto dejar usuario = { nombre: "Juan" }; // copiamos la fuerte referencia al objeto en la variable admin dejar administrador = usuario; // sobrescribamos el valor de la variable de usuario usuario = nulo; // aún se puede acceder al objeto a través de la variable admin
El objeto { name: "John" }
solo se eliminaría de la memoria si no hubiera referencias sólidas a él (si también sobrescribimos el valor de la variable admin
).
En JavaScript, existe un concepto llamado WeakRef
, que se comporta de manera ligeramente diferente en este caso.
Términos: “Referencia fuerte”, “Referencia débil”
Referencia fuerte : es una referencia a un objeto o valor que evita que el recolector de basura los elimine. De este modo, se mantiene el objeto o valor en la memoria al que apunta.
Esto significa que el objeto o valor permanece en la memoria y no es recogido por el recolector de basura mientras existan fuertes referencias activas a él.
En JavaScript, las referencias ordinarias a objetos son referencias fuertes. Por ejemplo:
// la variable de usuario tiene una fuerte referencia a este objeto dejar usuario = { nombre: "Juan" };
Referencia débil : es una referencia a un objeto o valor, que no impide que el recolector de basura los elimine. El recolector de basura puede eliminar un objeto o valor si las únicas referencias restantes a ellos son referencias débiles.
Nota de precaución
Antes de profundizar en ello, vale la pena señalar que el uso correcto de las estructuras analizadas en este artículo requiere una reflexión muy cuidadosa y, si es posible, es mejor evitarlas.
WeakRef
: es un objeto que contiene una referencia débil a otro objeto, llamado target
o referent
.
La peculiaridad de WeakRef
es que no impide que el recolector de basura elimine su objeto de referencia. En otras palabras, un objeto WeakRef
no mantiene vivo al objeto referent
.
Ahora tomemos la variable user
como "referente" y creemos una referencia débil a partir de ella a la variable admin
. Para crear una referencia débil, necesita usar el constructor WeakRef
, pasando el objeto de destino (el objeto al que desea una referencia débil).
En nuestro caso, esta es la variable user
:
// la variable de usuario tiene una fuerte referencia al objeto dejar usuario = { nombre: "Juan" }; // la variable admin contiene una referencia débil al objeto let admin = new WeakRef(usuario);
El siguiente diagrama muestra dos tipos de referencias: una referencia fuerte que usa la variable user
y una referencia débil que usa la variable admin
:
Luego, en algún momento, dejamos de usar la variable user
: se sobrescribe, sale del alcance, etc., mientras mantenemos la instancia WeakRef
en la variable admin
:
// sobrescribamos el valor de la variable de usuario usuario = nulo;
Una referencia débil a un objeto no es suficiente para mantenerlo “vivo”. Cuando las únicas referencias restantes a un objeto referente son referencias débiles, el recolector de basura es libre de destruir este objeto y usar su memoria para otra cosa.
Sin embargo, hasta que el objeto sea realmente destruido, la referencia débil puede devolverlo, incluso si no hay más referencias fuertes a este objeto. Es decir, nuestro objeto se convierte en una especie de “gato de Schrödinger”: no podemos saber con seguridad si está “vivo” o “muerto”:
En este punto, para obtener el objeto de la instancia WeakRef
, usaremos su método deref()
.
El método deref()
devuelve el objeto de referencia al que apunta WeakRef
, si el objeto todavía está en la memoria. Si el objeto ha sido eliminado por el recolector de basura, entonces el método deref()
devolverá undefined
:
let ref = admin.deref(); si (referencia) { // el objeto todavía es accesible: podemos realizar cualquier manipulación con él } demás { // el objeto ha sido recogido por el recolector de basura }
WeakRef
se utiliza normalmente para crear cachés o matrices asociativas que almacenan objetos que consumen muchos recursos. Esto permite evitar que el recolector de basura recopile estos objetos basándose únicamente en su presencia en la memoria caché o en la matriz asociativa.
Uno de los ejemplos principales es una situación en la que tenemos numerosos objetos de imagen binaria (por ejemplo, representados como ArrayBuffer
o Blob
) y queremos asociar un nombre o ruta con cada imagen. Las estructuras de datos existentes no son del todo adecuadas para estos fines:
El uso Map
para crear asociaciones entre nombres e imágenes, o viceversa, mantendrá los objetos de imagen en la memoria ya que están presentes en el Map
como claves o valores.
WeakMap
tampoco es elegible para este objetivo: porque los objetos representados como claves WeakMap
usan referencias débiles y no están protegidos contra la eliminación por parte del recolector de basura.
Pero, en esta situación, necesitamos una estructura de datos que utilice referencias débiles en sus valores.
Para ello, podemos utilizar una colección Map
, cuyos valores son instancias WeakRef
que se refieren a los objetos grandes que necesitamos. En consecuencia, no mantendremos estos objetos grandes e innecesarios en la memoria más tiempo del debido.
De lo contrario, esta es una forma de obtener el objeto de imagen del caché si aún es accesible. Si ha sido recolectado como basura, lo regeneraremos o lo volveremos a descargar.
De esta forma, se utiliza menos memoria en algunas situaciones.
A continuación se muestra un fragmento de código que demuestra la técnica de uso WeakRef
.
En resumen, utilizamos un Map
con claves de cadena y objetos WeakRef
como valores. Si el recolector de basura no ha recopilado el objeto WeakRef
, lo obtenemos del caché. De lo contrario, lo volvemos a descargar y lo guardamos en el caché para una posible reutilización adicional:
función recuperarImg() { // función abstracta para descargar imágenes... } función débilRefCache(fetchImg) { // (1) const imgCache = nuevo mapa(); // (2) return (nombreimg) => { // (3) const cachedImg = imgCache.get(imgName); // (4) if (cachedImg?.deref()) { // (5) devolver cachedImg?.deref(); } const newImg = fetchImg(imgName); // (6) imgCache.set(imgName, new WeakRef(newImg)); // (7) devolver nuevaImg; }; } const getCachedImg = débilRefCache(fetchImg);
Profundicemos en los detalles de lo sucedido aquí:
weakRefCache
: es una función de orden superior que toma otra función, fetchImg
, como argumento. En este ejemplo, podemos descuidar una descripción detallada de la función fetchImg
, ya que puede tener cualquier lógica para descargar imágenes.
imgCache
: es un caché de imágenes que almacena los resultados almacenados en caché de la función fetchImg
, en forma de claves de cadena (nombre de la imagen) y objetos WeakRef
como sus valores.
Devuelve una función anónima que toma el nombre de la imagen como argumento. Este argumento se utilizará como clave para la imagen almacenada en caché.
Intentando obtener el resultado almacenado en caché del caché, utilizando la clave proporcionada (nombre de la imagen).
Si el caché contiene un valor para la clave especificada y el recolector de basura no ha eliminado el objeto WeakRef
, devuelva el resultado almacenado en caché.
Si no hay ninguna entrada en el caché con la clave solicitada, o el método deref()
devuelve undefined
(lo que significa que el objeto WeakRef
ha sido recolectado como basura), la función fetchImg
descarga la imagen nuevamente.
Coloque la imagen descargada en el caché como un objeto WeakRef
.
Ahora tenemos una colección Map
, donde las claves (son nombres de imágenes como cadenas y los valores) son objetos WeakRef
que contienen las imágenes mismas.
Esta técnica ayuda a evitar la asignación de una gran cantidad de memoria para objetos que consumen muchos recursos y que ya nadie utiliza. También ahorra memoria y tiempo en caso de reutilizar objetos almacenados en caché.
Aquí hay una representación visual de cómo se ve este código:
Pero esta implementación tiene sus desventajas: con el tiempo, Map
se llenará con cadenas como claves que apuntan a un WeakRef
, cuyo objeto de referencia ya ha sido recolectado como basura:
Una forma de manejar este problema es limpiar periódicamente el caché y borrar las entradas "muertas". Otra forma es utilizar finalizadores, que exploraremos a continuación.
Otro caso de uso de WeakRef
es el seguimiento de objetos DOM.
Imaginemos un escenario en el que algún código o biblioteca de terceros interactúa con elementos de nuestra página siempre que existan en el DOM. Por ejemplo, podría ser una utilidad externa para monitorear y notificar sobre el estado del sistema (comúnmente llamado "registrador", un programa que envía mensajes informativos llamados "registros").
Ejemplo interactivo:
Resultado
index.js
índice.css
índice.html
const startMessagesBtn = document.querySelector('.start-messages'); // (1) const closeWindowBtn = document.querySelector('.window__button'); // (2) const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) startMessagesBtn.addEventListener('hacer clic', () => { // (4) startMessages(ventanaElementRef); startMessagesBtn.disabled = verdadero; }); closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) const mensajesInicio = (elemento) => { const temporizadorId = setInterval(() => { // (6) si (elemento.deref()) { // (7) carga útil constante = document.createElement("p"); payload.textContent = `Mensaje: Estado del sistema correcto: ${new Date().toLocaleTimeString()}`; element.deref().append(carga útil); } más { // (8) alert("El elemento ha sido eliminado."); // (9) clearInterval(temporizadorId); } }, 1000); };
.aplicación { pantalla: flexible; dirección flexible: columna; espacio: 16px; } .mensajes de inicio { ancho: contenido ajustado; } .ventana { ancho: 100%; borde: 2px sólido #464154; desbordamiento: oculto; } .ventana__encabezado { posición: pegajosa; relleno: 8px; pantalla: flexible; justificar contenido: espacio entre; alinear elementos: centro; color de fondo: #736e7e; } .ventana__título { margen: 0; tamaño de fuente: 24px; peso de fuente: 700; color: blanco; espaciado entre letras: 1px; } .botón__ventana { relleno: 4px; antecedentes: #4f495c; esquema: ninguno; borde: 2px sólido #464154; color: blanco; tamaño de fuente: 16px; cursor: puntero; } .ventana__cuerpo { altura: 250 píxeles; relleno: 16px; desbordamiento: desplazamiento; color de fondo: #736e7e33; }
<!TIPO DE DOCUMENTO HTML> <html lang="es"> <cabeza> <meta juego de caracteres="utf-8"> <enlace rel="hoja de estilo" href="index.css"> <título>Registrador DOM WeakRef</título> </cabeza> <cuerpo> <div clase="aplicación"> <button class="start-messages">Comenzar a enviar mensajes</button> <div clase="ventana"> <div class="ventana__header"> <p class="window__title">Mensajes:</p> <button class="window__button">Cerrar</button> </div> <div class="ventana__cuerpo"> Sin mensajes. </div> </div> </div> <script tipo="módulo" src="index.js"></script> </cuerpo> </html>
Cuando se hace clic en el botón “Comenzar a enviar mensajes”, en la llamada “ventana de visualización de registros” (un elemento con la clase .window__body
), comienzan a aparecer mensajes (registros).
Pero, tan pronto como este elemento se elimine del DOM, el registrador debería dejar de enviar mensajes. Para reproducir la eliminación de este elemento, simplemente haga clic en el botón "Cerrar" en la esquina superior derecha.
Para no complicar nuestro trabajo y no notificar al código de terceros cada vez que nuestro elemento DOM esté disponible, y cuando no lo esté, bastará con crear una referencia débil a él usando WeakRef
.
Una vez que el elemento se elimina del DOM, el registrador lo notará y dejará de enviar mensajes.
Ahora echemos un vistazo más de cerca al código fuente ( pestaña index.js
):
Obtenga el elemento DOM del botón "Comenzar a enviar mensajes".
Obtenga el elemento DOM del botón "Cerrar".
Obtenga el elemento DOM de la ventana de visualización de registros utilizando el new WeakRef()
. De esta manera, la variable windowElementRef
tiene una referencia débil al elemento DOM.
Agregue un detector de eventos en el botón "Comenzar a enviar mensajes", responsable de iniciar el registrador cuando se hace clic.
Agregue un detector de eventos en el botón "Cerrar", responsable de cerrar la ventana de visualización de registros cuando se hace clic.
Utilice setInterval
para comenzar a mostrar un mensaje nuevo cada segundo.
Si el elemento DOM de la ventana de visualización de registros aún es accesible y se mantiene en la memoria, cree y envíe un nuevo mensaje.
Si el método deref()
devuelve undefined
, significa que el elemento DOM se ha eliminado de la memoria. En este caso, el registrador deja de mostrar mensajes y borra el temporizador.
alert
, que se llamará después de que el elemento DOM de la ventana de visualización de registros se elimine de la memoria (es decir, después de hacer clic en el botón "Cerrar"). Tenga en cuenta que es posible que la eliminación de la memoria no se produzca de inmediato, ya que depende únicamente de los mecanismos internos del recolector de basura.
No podemos controlar este proceso directamente desde el código. Sin embargo, a pesar de esto, todavía tenemos la opción de forzar la recolección de basura desde el navegador.
En Google Chrome, por ejemplo, para hacer esto, necesita abrir las herramientas de desarrollador ( Ctrl + Shift + J en Windows/Linux u Opción + ⌘ + J en macOS), ir a la pestaña "Rendimiento" y hacer clic en el Botón del icono de papelera – “Recoger basura”:
Esta funcionalidad es compatible con la mayoría de los navegadores modernos. Una vez tomadas las acciones, la alert
se activará inmediatamente.
Ahora es el momento de hablar de finalizadores. Antes de continuar, aclaremos la terminología:
Devolución de llamada de limpieza (finalizador) : es una función que se ejecuta cuando el recolector de basura elimina de la memoria un objeto registrado en FinalizationRegistry
.
Su propósito es proporcionar la capacidad de realizar operaciones adicionales relacionadas con el objeto, después de que finalmente se haya eliminado de la memoria.
Registro (o FinalizationRegistry
): es un objeto especial en JavaScript que gestiona el registro y la cancelación del registro de objetos y sus devoluciones de llamada de limpieza.
Este mecanismo permite registrar un objeto para rastrearlo y asociarle una devolución de llamada de limpieza. Básicamente, es una estructura que almacena información sobre los objetos registrados y sus devoluciones de llamada de limpieza, y luego invoca automáticamente esas devoluciones de llamada cuando los objetos se eliminan de la memoria.
Para crear una instancia de FinalizationRegistry
, necesita llamar a su constructor, que toma un único argumento: la devolución de llamada de limpieza (finalizador).
Sintaxis:
función cleanupCallback(heldValue) { // código de devolución de llamada de limpieza } registro constante = nuevo FinalizationRegistry(cleanupCallback);
Aquí:
cleanupCallback
: una devolución de llamada de limpieza que se llamará automáticamente cuando un objeto registrado se elimine de la memoria.
heldValue
: el valor que se pasa como argumento a la devolución de llamada de limpieza. heldValue
es un objeto, el registro mantiene una fuerte referencia a él.
registry
: una instancia de FinalizationRegistry
.
Métodos de FinalizationRegistry
:
register(target, heldValue [, unregisterToken])
: se utiliza para registrar objetos en el registro.
target
: el objeto que se registra para el seguimiento. Si el target
es recolección de basura, se llamará a la devolución de llamada de limpieza heldValue
como argumento.
unregisterToken
opcional: un token de cancelación de registro. Se puede pasar para cancelar el registro de un objeto antes de que el recolector de basura lo elimine. Normalmente, el objeto target
se utiliza como unregisterToken
, que es la práctica estándar.
unregister(unregisterToken)
: el método unregister
se utiliza para cancelar el registro de un objeto del registro. Se necesita un argumento: unregisterToken
(el token de cancelación de registro que se obtuvo al registrar el objeto).
Pasemos ahora a un ejemplo sencillo. Usemos el objeto user
ya conocido y creemos una instancia de FinalizationRegistry
:
dejar usuario = { nombre: "Juan" }; registro constante = nuevo FinalizationRegistry((heldValue) => { console.log(`${heldValue} ha sido recopilado por el recolector de basura.`); });
Luego, registraremos el objeto, que requiere una devolución de llamada de limpieza llamando al método register
:
registro.registro(usuario,usuario.nombre);
El registro no mantiene una referencia fuerte al objeto que se registra, ya que esto iría en contra de su propósito. Si el registro mantuviera una referencia sólida, entonces el objeto nunca sería recolectado como basura.
Si el recolector de basura elimina el objeto, es posible que se llame a nuestra devolución de llamada de limpieza en algún momento en el futuro, y se le pase el heldValue
:
// Cuando el recolector de basura elimina el objeto de usuario, se imprimirá el siguiente mensaje en la consola: "John ha sido recogido por el recolector de basura".
También hay situaciones en las que, incluso en implementaciones que utilizan una devolución de llamada de limpieza, existe la posibilidad de que no se llame.
Por ejemplo:
Cuando el programa finaliza por completo su funcionamiento (por ejemplo, al cerrar una pestaña en un navegador).
Cuando la instancia FinalizationRegistry
ya no es accesible para el código JavaScript. Si el objeto que crea la instancia FinalizationRegistry
sale del alcance o se elimina, es posible que tampoco se invoquen las devoluciones de llamada de limpieza registradas en ese registro.
Volviendo a nuestro ejemplo de caché débil , podemos notar lo siguiente:
Aunque los valores incluidos en WeakRef
han sido recopilados por el recolector de basura, todavía existe un problema de "pérdida de memoria" en forma de claves restantes, cuyos valores han sido recopilados por el recolector de basura.
Aquí hay un ejemplo de almacenamiento en caché mejorado usando FinalizationRegistry
:
función recuperarImg() { // función abstracta para descargar imágenes... } función débilRefCache(fetchImg) { const imgCache = nuevo mapa(); registro constante = nuevo FinalizationRegistry((imgName) => { // (1) const cachedImg = imgCache.get(imgName); if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); }); return (nombreimg) => { const cachedImg = imgCache.get(imgName); si (cachedImg?.deref()) { devolver cachedImg?.deref(); } const newImg = fetchImg(imgName); imgCache.set(imgName, new WeakRef(newImg)); registro.register(newImg, imgName); // (2) devolver nuevaImg; }; } const getCachedImg = débilRefCache(fetchImg);
Para gestionar la limpieza de entradas de caché "inactivas", cuando el recolector de basura recopila los objetos WeakRef
asociados, creamos un registro de limpieza FinalizationRegistry
.
El punto importante aquí es que en la devolución de llamada de limpieza, se debe verificar si el recolector de basura eliminó la entrada y no la volvió a agregar, para no eliminar una entrada "activa".
Una vez que el nuevo valor (imagen) se descarga y se coloca en el caché, lo registramos en el registro del finalizador para rastrear el objeto WeakRef
.
Esta implementación contiene solo pares clave/valor reales o "activos". En este caso, cada objeto WeakRef
se registra en FinalizationRegistry
. Y después de que el recolector de basura limpie los objetos, la devolución de llamada de limpieza eliminará todos los valores undefined
.
Aquí hay una representación visual del código actualizado:
Un aspecto clave de la implementación actualizada es que los finalizadores permiten que se creen procesos paralelos entre el programa "principal" y las devoluciones de llamada de limpieza. En el contexto de JavaScript, el programa "principal" es nuestro código JavaScript, que se ejecuta en nuestra aplicación o página web.
Por lo tanto, desde el momento en que el recolector de basura marca un objeto para su eliminación y hasta la ejecución real de la devolución de llamada de limpieza, puede haber un cierto intervalo de tiempo. Es importante comprender que durante este intervalo de tiempo, el programa principal puede realizar cambios en el objeto o incluso devolverlo a la memoria.
Es por eso que, en la devolución de llamada de limpieza, debemos verificar si el programa principal ha agregado una entrada nuevamente al caché para evitar eliminar entradas "vivas". De manera similar, al buscar una clave en el caché, existe la posibilidad de que el recolector de basura haya eliminado el valor, pero la devolución de llamada de limpieza aún no se haya ejecutado.
Estas situaciones requieren atención especial si trabaja con FinalizationRegistry
.
Pasando de la teoría a la práctica, imagine un escenario de la vida real, donde un usuario sincroniza sus fotos en un dispositivo móvil con algún servicio en la nube (como iCloud o Google Photos) y quiere verlas desde otros dispositivos. Además de la funcionalidad básica de ver fotografías, estos servicios ofrecen muchas funciones adicionales, por ejemplo:
Edición de fotografías y efectos de vídeo.
Creando “recuerdos” y álbumes.
Montaje de vídeo a partir de una serie de fotografías.
...y mucho más.
Aquí, como ejemplo, usaremos una implementación bastante primitiva de dicho servicio. El punto principal es mostrar un posible escenario de uso conjunto de WeakRef
y FinalizationRegistry
en la vida real.
Así es como se ve:
En el lado izquierdo, hay una biblioteca de fotografías en la nube (se muestran como miniaturas). Podemos seleccionar las imágenes que necesitamos y crear un collage, haciendo clic en el botón "Crear collage" en el lado derecho de la página. Luego, el collage resultante se puede descargar como imagen.
Para aumentar la velocidad de carga de la página, sería razonable descargar y mostrar miniaturas de fotografías en calidad comprimida . Pero, para crear un collage a partir de fotos seleccionadas, descárgalas y úsalas en calidad de tamaño completo .
A continuación podemos ver que el tamaño intrínseco de las miniaturas es de 240x240 píxeles. El tamaño fue elegido expresamente para aumentar la velocidad de carga. Además, no necesitamos fotografías a tamaño completo en modo de vista previa.
Supongamos que necesitamos crear un collage de 4 fotos: las seleccionamos y luego hacemos clic en el botón "Crear collage". En esta etapa, la función weakRefCache
que ya conocemos, comprueba si la imagen requerida está en la memoria caché. De lo contrario, lo descarga de la nube y lo guarda en el caché para su uso posterior. Esto sucede para cada imagen seleccionada:
Al prestar atención a la salida en la consola, puede ver cuáles de las fotos se descargaron de la nube; esto se indica con FETCHED_IMAGE . Dado que este es el primer intento de crear un collage, esto significa que en esta etapa el "caché débil" todavía estaba vacío y todas las fotos se descargaron de la nube y se colocaron en ella.
Pero, junto con el proceso de descarga de imágenes, también existe un proceso de limpieza de memoria por parte del recolector de basura. Esto significa que el recolector de basura elimina el objeto almacenado en la caché, al que nos referimos mediante una referencia débil. Y nuestro finalizador se ejecuta con éxito, eliminando así la clave mediante la cual se almacenó la imagen en el caché. CLEANED_IMAGE nos notifica al respecto:
A continuación, nos damos cuenta de que no nos gusta el collage resultante y decidimos cambiar una de las imágenes y crear una nueva. Para hacer esto, simplemente anule la selección de la imagen innecesaria, seleccione otra y haga clic nuevamente en el botón "Crear collage":
Pero esta vez no todas las imágenes se descargaron de la red, y una de ellas fue tomada del caché débil: el mensaje CACHED_IMAGE nos lo informa. Esto significa que en el momento de la creación del collage, el recolector de basura aún no había eliminado nuestra imagen y la tomamos audazmente del caché, reduciendo así la cantidad de solicitudes de red y acelerando el tiempo total del proceso de creación del collage:
"Juguemos" un poco más reemplazando una de las imágenes nuevamente y creando un nuevo collage:
Esta vez el resultado es aún más impresionante. De las 4 imágenes seleccionadas, 3 de ellas fueron tomadas del caché débil y solo una tuvo que ser descargada de la red. La reducción de la carga de la red fue de aproximadamente el 75%. Impresionante, ¿no?
Por supuesto, es importante recordar que dicho comportamiento no está garantizado y depende de la implementación y operación específicas del recolector de basura.
En base a esto, surge inmediatamente una pregunta completamente lógica: ¿por qué no utilizamos un caché normal, donde podemos administrar sus entidades nosotros mismos, en lugar de depender del recolector de basura? Así es, en la gran mayoría de los casos no es necesario utilizar WeakRef
y FinalizationRegistry
.
Aquí, simplemente demostramos una implementación alternativa de una funcionalidad similar, utilizando un enfoque no trivial con características de lenguaje interesantes. Aún así, no podemos confiar en este ejemplo si necesitamos un resultado constante y predecible.
Puede abrir este ejemplo en la zona de pruebas.
WeakRef
: diseñado para crear referencias débiles a objetos, lo que permite que el recolector de basura los elimine de la memoria si ya no hay referencias fuertes a ellos. Esto es beneficioso para abordar el uso excesivo de memoria y optimizar la utilización de los recursos del sistema en las aplicaciones.
FinalizationRegistry
: es una herramienta para registrar devoluciones de llamada, que se ejecutan cuando se destruyen objetos a los que ya no se hace referencia fuerte. Esto permite liberar recursos asociados con el objeto o realizar otras operaciones necesarias antes de eliminar el objeto de la memoria.