Medir el rendimiento del código JavaScript requiere herramientas más precisas que console.time o Date.now(). La Performance API del navegador proporciona timestamps de alta resolución, marcadores y medidas dentro del código, un observador para detectar long tasks automáticamente y acceso a las métricas Web Vitals que Google usa para evaluar la experiencia de usuario.
performance.now(): timestamps de alta resolución
performance.now() devuelve el tiempo en milisegundos desde que la página comenzó a navegar, con resolución de microsegundos. A diferencia de Date.now(), no está sujeto a los ajustes del reloj del sistema.
const inicio = performance.now();
// Código a medir
const resultado = operacionCostosa();
const duracion = performance.now() - inicio;
console.log(`Duración: ${duracion.toFixed(3)}ms`);
// Por razones de seguridad (Spectre), los navegadores
// reducen la resolución a 0.1ms en contextos no aislados
// y a 5µs en contextos con COOP/COEP habilitado
performance.mark y performance.measure
performance.mark crea puntos de referencia nombrados en la línea de tiempo del rendimiento. performance.measure calcula la duración entre dos marcas y la almacena como una entrada de rendimiento que DevTools puede visualizar:
async function cargarDatos() {
performance.mark('carga-inicio');
const raw = await fetch('/api/datos').then(r => r.json());
performance.mark('fetch-fin');
const procesado = procesarDatos(raw);
performance.mark('proceso-fin');
// Calcular duraciones
performance.measure('tiempo-fetch', 'carga-inicio', 'fetch-fin');
performance.measure('tiempo-proceso', 'fetch-fin', 'proceso-fin');
performance.measure('tiempo-total', 'carga-inicio', 'proceso-fin');
// Leer las medidas
const medidas = performance.getEntriesByType('measure');
medidas.forEach(m => {
console.log(`${m.name}: ${m.duration.toFixed(2)}ms`);
});
// Limpiar para no acumular entradas
performance.clearMarks();
performance.clearMeasures();
return procesado;
}
PerformanceObserver: detectar Long Tasks
Las Long Tasks son bloques de código que ocupan el hilo principal más de 50ms, haciendo que la UI se congele. PerformanceObserver las detecta de forma asíncrona sin tener que sondear:
const observer = new PerformanceObserver((lista) => {
for (const entrada of lista.getEntries()) {
console.warn(
`Long Task detectada: ${entrada.duration.toFixed(1)}ms`,
`en ${entrada.attribution?.[0]?.name ?? 'desconocido'}`
);
}
});
observer.observe({ type: 'longtask', buffered: true });
// Desconectar cuando ya no sea necesario
// observer.disconnect();
Web Vitals con JavaScript puro
Las Core Web Vitals (LCP, CLS, INP) se pueden medir directamente con PerformanceObserver sin librerías externas:
// Largest Contentful Paint (LCP)
let lcp = 0;
new PerformanceObserver((lista) => {
const entradas = lista.getEntries();
lcp = entradas[entradas.length - 1].startTime;
}).observe({ type: 'largest-contentful-paint', buffered: true });
// Cumulative Layout Shift (CLS)
let cls = 0;
new PerformanceObserver((lista) => {
for (const entrada of lista.getEntries()) {
if (!entrada.hadRecentInput) cls += entrada.value;
}
}).observe({ type: 'layout-shift', buffered: true });
// Interaction to Next Paint (INP) métricas de respuesta a input
let maxDuracion = 0;
new PerformanceObserver((lista) => {
for (const entrada of lista.getEntries()) {
const duracion = entrada.processingStart - entrada.startTime;
if (duracion > maxDuracion) maxDuracion = duracion;
}
}).observe({ type: 'event', durationThreshold: 16, buffered: true });
// Reportar al servidor cuando el usuario abandone la página
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/api/vitals', JSON.stringify({
url: location.href,
lcp: lcp.toFixed(0),
cls: cls.toFixed(4),
inp: maxDuracion.toFixed(0),
}));
}
});
Medir el uso de memoria
En Chromium, performance.memory (no estándar) expone métricas básicas de heap. La API estándar performance.measureUserAgentSpecificMemory() (Chrome 89+) proporciona un desglose más detallado pero requiere contexto aislado:
// No estándar, solo Chromium
if (performance.memory) {
console.log('Heap usado:', (performance.memory.usedJSHeapSize / 1e6).toFixed(1), 'MB');
console.log('Heap total:', (performance.memory.totalJSHeapSize / 1e6).toFixed(1), 'MB');
}
// Estándar (requiere COOP/COEP)
if ('measureUserAgentSpecificMemory' in performance) {
const medicion = await performance.measureUserAgentSpecificMemory();
console.log('Memoria total estimada:', medicion.bytes / 1024, 'KB');
medicion.breakdown.forEach(b =>
console.log(b.types.join(', '), '->', (b.bytes / 1024).toFixed(1), 'KB')
);
}
La combinación de performance.mark/measure para código propio y PerformanceObserver para eventos del navegador da una imagen completa del rendimiento de la aplicación, directamente en el código JavaScript y sin depender de herramientas externas de monitorización.
