Panoramica
Le performance JavaScript incidono direttamente sulla percezione di velocità e sulla conversione. In questa guida vediamo come ridurre il lavoro sul main thread con code splitting e lazy‑loading, rendere le interazioni fluide con debounce/throttle e misurare le metriche chiave come LCP e INP.
Caricamento e code splitting
Carica solo ciò che serve, quando serve. Usa import()
dinamico per
suddividere il bundle per route/feature e caricare on‑demand.
Dynamic import su azione utente
document.getElementById('open-chart').addEventListener('click', async () => {
const { renderChart } = await import(/* webpackChunkName: "chart" */ './chart.js');
renderChart();
});
Lazy‑loading con IntersectionObserver
const el = document.querySelector('#reviews-widget');
const io = new IntersectionObserver(async ([entry], obs) => {
if (!entry.isIntersecting) return;
const mod = await import('./reviews-widget.js');
mod.mount(el);
obs.disconnect();
});
io.observe(el);
Suggerimenti: evita side‑effect globali nei moduli caricati on‑demand, esporta
funzioni pure; usa preload
/prefetch
per migliorare la
percezione nelle navigazioni successive.
Interazioni fluide: debounce e throttle
Riduci il lavoro per eventi ad alta frequenza (scroll, resize, input) con funzioni di controllo del rate.
Debounce
function debounce(fn, wait = 200) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn.apply(null, args), wait);
};
}
const onSearch = debounce((q) => fetch('/api?q='+encodeURIComponent(q)), 300);
const input = document.querySelector('#search');
input.addEventListener('input', (e) => onSearch(e.target.value));
Throttle
function throttle(fn, limit = 100) {
let last = 0, timer;
return (...args) => {
const now = Date.now();
if (now - last >= limit) {
last = now;
fn.apply(null, args);
} else if (!timer) {
const remaining = limit - (now - last);
timer = setTimeout(() => {
last = Date.now();
timer = null;
fn.apply(null, args);
}, remaining);
}
};
}
window.addEventListener('scroll', throttle(() => {
// lavoro leggero: aggiornare progress bar, ecc.
}, 100));
Preferisci operazioni idempotenti e evita layout thrashing (misurazioni/forzature di
layout ripetute). Usa requestAnimationFrame
per aggiornamenti visivi.
Misurare e monitorare le performance
Misura prima di ottimizzare. Traccia le Core Web Vitals e osserva gli impatti sul main thread.
Osservare LCP
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const last = entries[entries.length - 1];
console.log('LCP', last.startTime.toFixed(0), 'ms');
}).observe({ type: 'largest-contentful-paint', buffered: true });
Osservare INP
new PerformanceObserver((entryList) => {
for (const e of entryList.getEntries()) {
if (e.name !== 'click' && e.name !== 'keydown' && e.name !== 'pointerdown') continue;
console.log('INP candidate', Math.max(e.processingStart - e.startTime, e.duration).toFixed(0), 'ms');
}
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });
In produzione usa web-vitals
per calcolare INP finale e invia i dati a
un endpoint RUM per ottimizzazioni iterative.
Checklist rapida
- Analizza il bundle: rimuovi dipendenze superflue e attiva minification/treeshaking
-
Applica code splitting per route/feature e lazy‑loading con
import()
- Debounce/throttle per eventi ad alta frequenza (scroll, resize, input)
-
Evita lavoro pesante sul main thread; delega a
Web Worker
quando possibile - Monitora LCP/INP e correggi regressioni in CI/CD
FAQ
Che differenza c’è tra debounce e throttle?
Debounce posticipa l’esecuzione finché l’utente non si ferma; throttle limita la frequenza massima in un intervallo. Entrambi riducono il carico sul main thread.
Quando conviene il lazy‑loading?
Per widget fuori viewport, grafici, editor ricchi o route non essenziali al primo paint. Migliora TTI/INP percepita.
Come misuro LCP/INP in produzione?
Usa la libreria web-vitals
per ottenere valori standardizzati e
inviali a un endpoint di raccolta (RUM) per analisi.
Commenti
- Categoria: JavaScript
- Aggiornato: