
Entrada de front-end (vue) al curso de competencia: ingresar para aprender JavaScript no proporciona ninguna operación de administración de memoria. En cambio, la máquina virtual JavaScript administra la memoria a través de un proceso de recuperación de memoria llamado recolección de basura .
Dado que no podemos forzar la recolección de basura, ¿cómo sabemos que está funcionando? ¿Cuánto sabemos al respecto?
La ejecución del script se pausa durante este proceso.
Libera memoria para recursos inaccesibles.
es incierto
No verifica toda la memoria a la vez, sino que se ejecuta en múltiples ciclos.
Es impredecible pero funcionará cuando sea necesario.
¿Significa esto que no hay necesidad de preocuparse por problemas de asignación de memoria y recursos? Si no tenemos cuidado, pueden producirse algunas pérdidas de memoria.

Una pérdida de memoria es un bloque de memoria asignada que el software no puede recuperar.
Javascript proporciona un recolector de basura, pero eso no significa que podamos evitar pérdidas de memoria. Para ser elegible para la recolección de basura, no se debe hacer referencia al objeto en ningún otro lugar. Si tiene referencias a recursos no utilizados, esto evitará que esos recursos sean reclamados. A esto se le llama retención de memoria inconsciente .
La pérdida de memoria puede hacer que el recolector de basura se ejecute con más frecuencia. Dado que este proceso impedirá que el script se ejecute, puede provocar que nuestro programa se congele. Si se produce tal retraso, los usuarios exigentes definitivamente notarán que si no están satisfechos con él, el producto estará fuera de línea durante mucho tiempo. Lo que es más grave, puede provocar que toda la aplicación falle, lo cual es gg.
¿Cómo prevenir pérdidas de memoria? Lo principal es que debemos evitar retener recursos innecesarios. Veamos algunos escenarios comunes.
setInterval() llama repetidamente a una función o ejecuta un fragmento de código, con un retraso de tiempo fijo entre cada llamada. Devuelve un ID de intervalo ID identifica de forma única el intervalo para que luego puedas eliminarlo llamando clearInterval() .
Creamos un componente que llama a una función de devolución de llamada para indicar que ha finalizado después de x número de bucles. Estoy usando React en este ejemplo, pero funciona con cualquier marco FE.
importar React, {useRef} de 'react';
const Temporizador = ({ ciclos, onFinish }) => {
const ciclos actuales = useRef(0);
establecerIntervalo(() => {
if (ciclosactuales.actual>= ciclos) {
al finalizar();
devolver;
}
ciclosactuales.current++;
}, 500);
devolver (
<p>Cargando...</p>
);
}
exportar temporizador predeterminado;A primera vista no parece haber ningún problema. No se preocupe, creemos otro componente que active este temporizador y analicemos el rendimiento de su memoria.
importar React, {useState} de 'react';
importar estilos desde '../styles/Home.module.css'
importar temporizador desde '../components/Timer';
exportar función predeterminada Inicio() {
const [showTimer, setShowTimer] = useState();
const onFinish = () => setShowTimer(false);
devolver (
<p className={estilos.contenedor}>
{mostrar temporizador? (
<Ciclos del temporizador={10} onFinish={onFinish} />
): (
<botón onClick={() => setShowTimer(true)}>
Rever
</botón>
)}
</p>
)
} Después de algunos clics en el botón Retry , este es el resultado de usar Chrome Dev Tools para obtener el uso de memoria:

Cuando hacemos clic en el botón Reintentar, podemos ver que cada vez se asigna más memoria. Esto significa que la memoria previamente asignada no ha sido liberada. El cronómetro sigue funcionando en lugar de ser reemplazado.
¿Cómo solucionar este problema? El valor de retorno de setInterval es un ID de intervalo, que podemos usar para cancelar este intervalo. En este caso particular, podemos llamar clearInterval después de descargar el componente.
utilizarEfecto(() => {
const intervaloId = setInterval(() => {
if (ciclosactuales.actual>= ciclos) {
al finalizar();
devolver;
}
ciclosactuales.current++;
}, 500);
return () => clearInterval(intervalId);
}, [])A veces, es difícil encontrar este problema al escribir código. La mejor manera es abstraer los componentes.
Usando React aquí, podemos envolver toda esta lógica en un Hook personalizado.
importar {useEffect} desde 'reaccionar';
exportar const useTimeout = (refreshCycle = 100, devolución de llamada) => {
utilizarEfecto(() => {
si (ciclo de actualización <= 0) {
setTimeout(devolución de llamada, 0);
devolver;
}
const intervaloId = setInterval(() => {
llamar de vuelta();
}, actualizar ciclo);
return () => clearInterval(intervalId);
}, [refreshCycle, setInterval, clearInterval]);
};
exportar useTimeout predeterminado; Ahora, siempre que necesites usar setInterval , puedes hacer esto:
const handleTimeout = () => ...; useTimeout(100, handleTimeout);
Ahora puede utilizar este useTimeout Hook sin preocuparse por las pérdidas de memoria, que también es el beneficio de la abstracción.
Web API proporciona una gran cantidad de detectores de eventos. Anteriormente, analizamos setTimeout . Ahora veamos addEventListener .
En este ejemplo, creamos una función de atajo de teclado. Dado que tenemos diferentes funciones en diferentes páginas, se crearán diferentes funciones de teclas de acceso directo
función inicioAtajos({ clave}) {
si (clave === 'E') {
console.log('editar widget')
}
}
// Cuando el usuario inicia sesión en la página de inicio, ejecutamos document.addEventListener('keyup', homeShortcuts);
// El usuario hace algo y luego navega a la función de configuración settingsShortcuts({ key}) {
si (clave === 'E') {
console.log('editar configuración')
}
}
// Cuando el usuario inicia sesión en la página de inicio, ejecutamos document.addEventListener('keyup', settingsShortcuts); Todavía se ve bien, excepto que la keyup anterior no se limpia al ejecutar el segundo addEventListener . En lugar de reemplazar nuestro detector keyup , este código agregará otra callback . Esto significa que cuando se presiona una tecla, se activan dos funciones.
Para borrar la devolución de llamada anterior, necesitamos usar removeEventListener :
document.removeEventListener('keyup', homeShortcuts);Refactorice el código anterior:
función inicioAtajos({ clave}) {
si (clave === 'E') {
console.log('editar widget')
}
}
// el usuario llega a casa y ejecutamos
document.addEventListener('keyup', homeShortcuts);
// el usuario hace algunas cosas y navega a la configuración
configuración de funcionesAtajos({ clave}) {
si (clave === 'E') {
console.log('editar configuración')
}
}
// el usuario llega a casa y ejecutamos
document.removeEventListener('keyup', homeShortcuts);
document.addEventListener('keyup', settingsShortcuts);Como regla general, tenga mucho cuidado al utilizar herramientas de objetos globales.
Los observadores son una característica de la API web del navegador que muchos desarrolladores desconocen. Esto es potente si desea comprobar si hay cambios en la visibilidad o el tamaño de los elementos HTML.
La interfaz IntersectionObserver (parte de la API Intersection Observer) proporciona un método para observar de forma asincrónica el estado de intersección de un elemento de destino con sus elementos ancestros o viewport de documentos de nivel superior. El elemento ancestro y viewport se denominan root .
Aunque es potente, debemos utilizarlo con precaución. Una vez que hayas terminado de observar un objeto, recuerda cancelarlo cuando no esté en uso.
Echa un vistazo al código:
referencia constante = ...
constante visible = (visible) => {
console.log(`Es ${visible}`);
}
utilizarEfecto(() => {
si (!ref) {
devolver;
}
observador.actual = nuevo IntersectionObserver(
(entradas) => {
si (!entradas[0].isIntersecting) {
visible (verdadero);
} demás {
visible(falso);
}
},
{ rootMargin: `-${header.height}px` },
);
observador.actual.observar(ref);
}, [referencia]); El código anterior se ve bien. Sin embargo, ¿qué le sucede al observador una vez que se descarga el componente? No se borra y se pierde la memoria. ¿Cómo solucionamos este problema? Simplemente use el método disconnect :
referencia constante = ...
constante visible = (visible) => {
console.log(`Es ${visible}`);
}
utilizarEfecto(() => {
si (!ref) {
devolver;
}
observador.actual = nuevo IntersectionObserver(
(entradas) => {
si (!entradas[0].isIntersecting) {
visible (verdadero);
} demás {
visible(falso);
}
},
{ rootMargin: `-${header.height}px` },
);
observador.actual.observar(ref);
return () => observador.current?.disconnect();
}, [referencia]); Agregar objetos a una ventana es un error común. En algunos escenarios, puede resultar difícil encontrarlo, especialmente cuando se utiliza this en un contexto de ejecución de ventana. Eche un vistazo al siguiente ejemplo:
función agregarElemento(elemento) {
si (!esta.pila) {
esta.pila = {
elementos: []
}
}
esta.pila.elementos.push(elemento);
} Parece inofensivo, pero depende del contexto desde el que llame addElement . Si llama a addElement desde el contexto de la ventana, el montón crecerá.
Otro problema podría ser definir incorrectamente una variable global:
var a = 'ejemplo 1'; // El alcance está limitado al lugar donde se crea var b = 'ejemplo 2' // Agregado al objeto Ventana;
Para evitar este problema puedes usar el modo estricto:
"uso estricto"
Al utilizar el modo estricto, le indica al compilador de JavaScript que desea protegerse de estos comportamientos. Aún puedes usar Windows cuando lo necesites. Sin embargo, debes usarlo de manera explícita.
Cómo afecta el modo estricto a nuestro ejemplo anterior:
Para la función addElement , this no está definido cuando se llama desde el ámbito global
Si no especifica const | let | var en una variable, obtendrá el siguiente error:
Error de referencia no detectado: b no está definido
Los nodos DOM tampoco son inmunes a las pérdidas de memoria. Debemos tener cuidado de no guardar referencias a ellos. De lo contrario, el recolector de basura no podrá limpiarlos porque seguirán siendo accesibles.
Demuéstrelo con un pequeño fragmento de código:
elementos constantes = [];
lista constante = document.getElementById('lista');
función agregarElemento() {
// limpiar nodos
lista.innerHTML = '';
const pElement= document.createElement('p');
elemento const = document.createTextNode(`agregando elemento ${elements.length}`);
pElement.appendChild(elemento);
lista.appendChild(pElement);
elementos.push(pElement);
}
document.getElementById('addElement').onclick = addElement; Tenga en cuenta que la función addElement borra la lista p y le agrega un nuevo elemento como elemento secundario. Este elemento recién creado se agrega a la matriz elements .
La próxima vez que se ejecute addElement , el elemento se eliminará de la lista p , pero no es adecuado para la recolección de basura porque está almacenado en la matriz elements .
Monitorizamos la función después de ejecutarla varias veces:
Vea cómo se vio comprometido el nodo en la captura de pantalla anterior. Entonces, ¿cómo solucionar este problema? Limpiar la matriz elements los hará elegibles para la recolección de basura.
En este artículo, analizamos las formas más comunes de pérdida de memoria. Es obvio que JavaScript en sí no pierde memoria. En cambio, es causado por una retención de memoria involuntaria por parte del desarrollador. Mientras el código esté limpio y no nos olvidemos de limpiarlo nosotros mismos, no se producirán fugas.
Es imprescindible comprender cómo funcionan la memoria y la recolección de basura en JavaScript. Algunos desarrolladores tienen la falsa sensación de que, dado que es automático, no necesitan preocuparse por este problema.