Comprender las referencias débiles en JavaScript

En este artículo, Frank Joseph explica las referencias fuertes y débiles en JavaScript, así como el concepto de accesibilidad. ¡Vamos a profundizar en!
La gestión de la memoria y el rendimiento son aspectos importantes del desarrollo de software y a los que todo desarrollador de software debe prestar atención. Aunque son útiles, las referencias débiles no se utilizan con frecuencia en JavaScript. WeakSet
y WeakMap
conocieron JavaScript en la versión ES6.
Referencia débil
Para aclarar, a diferencia de la referencia fuerte, la referencia débil no impide que el recolector de basura reclame o recopile el objeto al que se hace referencia, incluso si es la única referencia al objeto en la memoria.
Antes de entrar en referencias fuertes, WeakSet
, Set
, WeakMap
y Map
, ilustremos la referencia débil con el siguiente fragmento:
// Create an instance of the WeakMap object.let human = new WeakMap():// Create an object, and assign it to a variable called man.let man = { name: "Joe Doe" };// Call the set method on human, and pass two arguments (key and value) to it.human.set(man, "done")console.log(human)
El resultado del código anterior sería el siguiente:
WeakMap {{…} = 'done'}man = null;console.log(human)
El man
argumento ahora está establecido en el WeakMap
objeto. En el momento en que reasignamos la man
variable a null
, la única referencia al objeto original en la memoria era la referencia débil, y provenía del WeakMap
que creamos anteriormente. Cuando el motor JavaScript ejecuta un proceso de recolección de basura, el man
objeto se eliminará de la memoria y de la WeakMap
que le asignamos. Esto se debe a que es una referencia débil y no impide la recolección de basura.
Parece que estamos progresando. Hablemos de referencias fuertes y luego uniremos todo.
Referencia fuerte
Una referencia fuerte en JavaScript es una referencia que evita que un objeto sea recolectado como basura. Mantiene el objeto en la memoria.
Los siguientes fragmentos de código ilustran el concepto de referencia fuerte:
let man = {name: "Joe Doe"};let human = [man];man = null;console.log(human);
El resultado del código anterior sería este:
// An array of objects of length 1. [{…}]
Ya no se puede acceder al objeto a través de la dog
variable debido a la fuerte referencia que existe entre la human
matriz y el objeto. El objeto se retiene en la memoria y se puede acceder a él con el siguiente código:
console.log(human[0])
El punto importante a tener en cuenta aquí es que una referencia débil no impide que un objeto sea recolectado como basura, mientras que una referencia fuerte sí previene que un objeto sea recolectado como basura.
Recolección de basura en JavaScript
Como en todo lenguaje de programación, la gestión de la memoria es un factor clave a considerar al escribir JavaScript. A diferencia de C, JavaScript es un lenguaje de programación de alto nivel que asigna memoria automáticamente cuando se crean objetos y que la borra automáticamente cuando los objetos ya no son necesarios. El proceso de borrar la memoria cuando los objetos ya no se utilizan se conoce como recolección de basura. Es casi imposible hablar de recolección de basura en JavaScript sin tocar el concepto de accesibilidad.
Accesibilidad
Todos los valores que están dentro de un alcance específico o que están en uso dentro de un alcance se consideran "alcanzables" dentro de ese alcance y se denominan "valores alcanzables". Los valores alcanzables siempre se almacenan en la memoria.
Los valores se consideran alcanzables si son:
- valores en la raíz del programa o referenciados desde la raíz, como variables globales o la función que se está ejecutando actualmente, su contexto y devolución de llamada;
- valores accesibles desde la raíz mediante una referencia o cadena de referencias (por ejemplo, un objeto en la variable global que hace referencia a otro objeto, que también hace referencia a otro objeto; todos estos se consideran valores alcanzables).
Los fragmentos de código siguientes ilustran el concepto de accesibilidad:
let languages = {name: “JavaScript”};
Aquí tenemos un objeto con un par clave-valor (con el nombre JavaScript
) que hace referencia a la variable global languages
. Si sobrescribimos el valor de languages
asignándole null
…
languages = null;
… entonces el objeto será recolectado como basura y JavaScript
no se podrá volver a acceder al valor. Aquí hay otro ejemplo:
let languages = {name: “JavaScript”};let programmer = languages;
A partir de los fragmentos de código anteriores, podemos acceder a la propiedad del objeto tanto desde la languages
variable como desde la programmer
variable. Sin embargo, si nos ponemos languages
a null
…
languages = null;
… entonces el objeto todavía estará en la memoria porque se puede acceder a él a través de la programmer
variable. Así es en pocas palabras cómo funciona la recolección de basura.
Nota: De forma predeterminada, JavaScript utiliza referencias sólidas para sus referencias. Para implementar una referencia débil en JavaScript, usaría WeakMap
, WeakSet
o WeakRef
.
Comparación de conjunto y conjunto débil
Un objeto establecido es una colección de valores únicos con una sola aparición. Un conjunto, como una matriz, no tiene un par clave-valor. Podemos iterar a través de un conjunto de matrices con los métodos de matriz for… of
y .forEach
.
Ilustremos esto con los siguientes fragmentos:
let setArray = new Set(["Joseph", "Frank", "John", "Davies"]);for (let names of setArray){ console.log(names)}// Joseph Frank John Davies
.forEach
También podemos usar el iterador:
setArray.forEach((name, nameAgain, setArray) ={ console.log(names); });
A WeakSet
es una colección de objetos únicos. Como sugiere el nombre, WeakSet
los s utilizan referencias débiles. Las siguientes son propiedades de WeakSet()
:
- Sólo puede contener objetos.
- Se puede acceder a los objetos dentro del conjunto en otro lugar.
- No se puede recorrer en bucle.
- Me gusta
Set()
,WeakSet()
tiene los métodosadd
,has
ydelete
.
El siguiente código ilustra cómo utilizarlo WeakSet()
y algunos de los métodos disponibles:
const human = new WeakSet();let paul = {name: "Paul"};let mary = {gender: "Mary"};// Add the human with the name paul to the classroom. const classroom = human.add(paul);console.log(classroom.has(paul)); // truepaul = null;// The classroom will be cleaned automatically of the human paul.console.log(classroom.has(paul)); // false
En la línea 1, hemos creado una instancia de WeakSet()
. En las líneas 3 y 4, creamos objetos y los asignamos a sus respectivas variables. En la línea 7, agregamos paul
y WeakSet()
lo asignamos a la classroom
variable. En la línea 11 hicimos la paul
referencia null
. El código de la línea 15 regresa false
porque WeakSet()
se limpiará automáticamente; por lo tanto, WeakSet()
no impide la recolección de basura.
Comparación de mapas y mapas débiles
Como sabemos por la sección anterior sobre recolección de basura, el motor JavaScript mantiene un valor en la memoria siempre que sea accesible. Ilustremos esto con algunos fragmentos:
let smashing = {name: "magazine"};// The object can be accessed from the reference.// Overwrite the reference smashing.smashing = null;// The object can no longer be accessed.
Las propiedades de una estructura de datos se consideran accesibles mientras la estructura de datos está en la memoria y, por lo general, se mantienen en la memoria. Si almacenamos un objeto en una matriz, mientras la matriz esté en la memoria, aún se puede acceder al objeto incluso si no tiene otras referencias.
let smashing = {name: "magazine"};let arr = [smashing];// Overwrite the reference.smashing = null;console.log(array[0]) // {name: 'magazine'}
Aún podemos acceder a este objeto incluso si la referencia se sobrescribió porque el objeto se guardó en la matriz; por lo tanto, se guardó en la memoria mientras la matriz todavía esté en la memoria. Por lo tanto, no fue recogido como basura. Como hemos usado una matriz en el ejemplo anterior, map
también podemos usarla. Mientras map
todavía exista, los valores almacenados en él no serán recolectados como basura.
let map = new Map();let smashing {name: "magazine"};map.set(smashing, "blog");// Overwrite the reference.smashing = null;// To access the object.console.log(map.keys());
Como un objeto, map
s puede contener pares clave-valor y podemos acceder al valor a través de la clave. Pero con map
s, debemos usar el .get()
método para acceder a los valores.
Según Mozilla Developer Network, el Map
objeto contiene pares clave-valor y recuerda el orden de inserción original de las claves. Cualquier valor (tanto objetos como valores primitivos ) se puede utilizar como clave o valor.
A diferencia de a map
, WeakMap
tiene una referencia débil; por lo tanto, no impide que la recolección de basura elimine los valores a los que hace referencia si esos valores no están fuertemente referenciados en otro lugar. Aparte de esto, WeakMap
es lo mismo que map
. WeakMap
Los s no son enumerables debido a referencias débiles.
Con WeakMap
, las claves deben ser objetos y los valores pueden ser un número o una cadena.
Los fragmentos a continuación ilustran cómo WeakMap
funciona y los métodos que contiene:
// Create a weakMap.let weakMap = new WeakMap();let weakMap2 = new WeakMap();// Create an object.let ob = {};// Use the set method.weakMap.set(ob, "Done");// You can set the value to be an object or even a function.weakMap.set(ob, ob)// You can set the value to undefined.weakMap.set(ob, undefined);// WeakMap can also be the value and the key.weakMap.set(weakMap2, weakMap)// To get values, use the get method.weakMap.get(ob) // Done// Use the has method.weakMap.has(ob) // trueweakMap.delete(ob)weakMap.has(ob) // false
Un efecto secundario importante de usar objetos como claves en un WeakMap
sin otras referencias es que se eliminarán automáticamente de la memoria durante la recolección de basura.
Áreas de aplicación de WeakMap
WeakMap
Se puede utilizar en dos áreas del desarrollo web: almacenamiento en caché y almacenamiento de datos adicional.
Almacenamiento en caché
Esta es una técnica web que implica guardar (es decir, almacenar) una copia de un recurso determinado y devolverla cuando se solicite. El resultado de una función se puede almacenar en caché para que cada vez que se llame a la función, el resultado almacenado en caché se pueda reutilizar.
Veamos esto en acción. Crea un archivo, nómbralo cachedResult.js
y escribe lo siguiente en él:
let cachedResult = new WeakMap(); // A function that stores a result.function keep(obj){if(!cachedResult.has(obj){ let result = obj; cachedResult.set(obj, result); }return cachedResult.get(obj);}let obj = {name: "Frank"};let resultSaved = keep(obj)obj = null;// console.log(cachedResult.size); Possible with map, not with WeakMap
Si hubiéramos usado Map()
en lugar de WeakMap()
en el código anterior y hubiera múltiples invocaciones en la función keep()
, entonces solo calcularía el resultado la primera vez que se llamara y lo recuperaría de cachedResult
las otras veces. El efecto secundario es que necesitaremos limpiar cachedResult
siempre que el objeto no sea necesario. Con WeakMap()
, el resultado almacenado en caché se eliminará automáticamente de la memoria tan pronto como se recolecte la basura del objeto. El almacenamiento en caché es un excelente medio para mejorar el rendimiento del software: podría ahorrar costos de uso de bases de datos, llamadas API de terceros y solicitudes de servidor a servidor. Con el almacenamiento en caché, se guarda localmente una copia del resultado de una solicitud.
Datos adicionales
Otro uso importante WeakMap()
es el almacenamiento de datos adicional. Imagine que estamos construyendo una plataforma de comercio electrónico y tenemos un programa que cuenta a los visitantes y queremos poder reducir el recuento cuando los visitantes se van. Esta tarea sería muy exigente con Map, pero bastante fácil de implementar con WeakMap()
:
let visitorCount = new WeakMap();function countCustomer(customer){ let count = visitorCount.get(customer) || 0; visitorCount.set(customer, count + 1);}
Creemos un código de cliente para esto:
let person = {name: "Frank"};// Taking count of person visit.countCustomer(person)// Person leaves.person = null;
Con Map()
, tendremos que limpiar visitorCount
cada vez que se vaya un cliente; de lo contrario, crecerá en la memoria indefinidamente, ocupando espacio. Pero con WeakMap()
, no necesitamos limpiar visitorCount
; Tan pronto como una persona (objeto) se vuelve inalcanzable, se recolectará automáticamente como basura.
Conclusión
En este artículo, aprendimos sobre la referencia débil, la referencia fuerte y el concepto de accesibilidad, y tratamos de conectarlos con la gestión de la memoria lo mejor que pudimos. Espero que hayas encontrado valioso este artículo. No dudes en dejar un comentario.
(il, al, yk)Explora más en
- javascript
- Actuación
- Codificación
Deja un comentario