Nómada Tasques funciona i està provada: 124 proves en quatre segons i tres recorreguts E2E confirmen que fa el que ha de fer. Però el Taller Nómada fa dos anys que la fa servir i el tauler ja no té sis tasques: en té sis-centes. La Marta diu que «va lenta». L'Iván diu que «en escriure al cercador s'encalla». La Lucía diu que «triga molt a carregar». Tres frases, tres problemes possiblement diferents, i cap de les tres és accionable. Abans de tocar ni una sola línia cal convertir «va lenta» en un número, aquest número en un objectiu, i aquest objectiu en una prova que falli quan s'incompleixi. Aquesta lliçó ensenya aquesta disciplina: què significa «ràpid» per a una persona, quines mètriques ho capturen (els Web Vitals), amb quines eines s'obtenen (Lighthouse, el panell Performance, el panell Network, la User Timing API), com es fa un mesurament honest que no es deixa enganyar pel soroll ni per la màquina de qui desenvolupa, i com es fixa un pressupost de rendiment que la integració contínua vigili tota sola. En acabar tindràs la línia base de Nómada Tasques amb 600 tasques: la taula de números que les quatre lliçons següents milloraran.
Contingut
- Per què la intuïció falla (i què va dir Knuth de debò)
- Què percep una persona: 100 ms, 1 s i 16,7 ms
- Els Web Vitals, un per un
- Dades de laboratori i dades de camp
- Lighthouse: llegir l'informe sense obsessionar-se amb la puntuació
- El panell Performance, pas a pas
- El panell Network i l'estrangulament de xarxa i CPU
- Instrumentar el teu propi codi:
performance.now()i la User Timing API - Mesurar en producció:
PerformanceObserveriweb-vitals - Com es fa un benchmark honest
- La teva màquina menteix: simular un mòbil modest
- La línia base de Nómada Tasques amb 600 tasques
- El pressupost de rendiment i la integració contínua
- Errors Habituals i Consells
- Exercicis
- Conclusió
- Per què la intuïció falla (i què va dir Knuth de debò)
A 08-01 vas aprendre que depurar per intuïció no convergeix: canvies coses, algunes semblen funcionar, i acabes amb un codi pitjor i un error que continua allà. El rendiment és idèntic, però pitjor, perquè l'error com a mínim es manifesta i la lentitud es pot negar («a mi em va bé»).
Hi ha una frase que se cita constantment i gairebé sempre malament:
«L'optimització prematura és l'arrel de tots els mals.»
La frase és de Donald Knuth, a Structured Programming with go to Statements (1974), i sencera diu una cosa força diferent:
«Ens hauríem d'oblidar de les petites eficiències, diguem el 97 % del temps: l'optimització prematura és l'arrel de tots els mals. Però no hauríem de deixar passar les nostres oportunitats en aquell 3 % crític.»
I unes línies abans, el paràgraf que ningú no cita i que és el que importa aquí:
«Sovint és un error fer judicis a priori sobre quines parts d'un programa són realment crítiques, ja que l'experiència universal dels programadors que han fet servir eines de mesurament és que les seves suposicions intuïtives fallen.»
Knuth no deia «no optimitzis». Deia «no optimitzis allò que no has mesurat», que és exactament el contrari de com se sol fer servir la cita. La seva recomanació explícita era instrumentar el programa, trobar el 3 % que consumeix el temps i treballar-hi amb tota la cura del món.
Per què falla la intuïció? Per tres motius concrets:
| Motiu | Exemple a Nómada Tasques |
|---|---|
| El cost no és on sembla | El sort de 600 tasques costa 0,4 ms; pintar-ne les 600 targetes costa 310 ms. Tothom mira el sort |
| Els ordres de magnitud enganyen | Canviar forEach per for estalvia microsegons; treure un getBoundingClientRect d'un bucle estalvia 400 ms |
| El coll d'ampolla es mou | En arreglar el render, el problema passa a ser la descàrrega; optimitzar el render un altre cop ja no aporta res |
La conseqüència pràctica és una regla de treball que aplicaràs a tot el mòdul:
flowchart LR
A["1 · Definir<br/>què és ràpid"] --> B["2 · Mesurar<br/>la línia base"]
B --> C["3 · Trobar<br/>el coll d'ampolla"]
C --> D["4 · Canviar<br/>UNA cosa"]
D --> E["5 · Mesurar<br/>un altre cop"]
E -->|"millora"| F["6 · Fixar<br/>el pressupost"]
E -->|"no millora"| G["Revertir"]
G --> C
F --> C
El pas 4 —canviar una sola cosa— és el que gairebé tothom se salta, i és el mateix que en depuració: si canvies cinc coses i millora, no saps quina ha servit, i probablement quatre només han afegit complexitat. El pas Revertir tampoc no és opcional: una optimització que no millora és deute tècnic pur.
- Què percep una persona: 100 ms, 1 s i 16,7 ms
«Ràpid» no és un número absolut: és el que el sistema perceptiu humà tolera. Hi ha tres llindars que convé tenir memoritzats perquè governen totes les decisions del mòdul.
| Llindar | Què significa | Què passa si se supera |
|---|---|---|
| ~100 ms | Límit de la sensació de resposta instantània a una acció pròpia | L'usuari percep que l'aplicació «reacciona», no que «respon» |
| ~1 s | Límit per mantenir el fil de pensament | Es nota l'espera, però no s'abandona la tasca mental |
| ~10 s | Límit de l'atenció | L'usuari canvia de pestanya, i cal mostrar progrés explícit |
| 16,7 ms | Pressupost d'un fotograma a 60 Hz | Es perd el fotograma: l'animació o el desplaçament «salta» |
El de 16,7 ms mereix explicació. Una pantalla a 60 Hz es refresca 60 vegades per segon: 1000 / 60 = 16,7 ms per fotograma. En aquest temps el navegador ha d'executar el teu JavaScript, recalcular estils, fer el layout, pintar i compondre. Si el teu gestor de scroll triga 20 ms, el fotograma no arriba a temps i es perd. I en pantalles de 120 Hz el pressupost baixa a 8,3 ms.
Com que el navegador necessita part d'aquest temps per a la seva feina, la regla pràctica és deixar menys de 10 ms de JavaScript per fotograma quan hi ha alguna cosa en moviment.
flowchart TD
subgraph F["Un fotograma a 60 Hz: 16,7 ms"]
direction LR
J["JS<br/>~10 ms"] --> S["Estil"] --> L["Layout"] --> P["Pintat"] --> C["Composició"]
end
F --> OK["Fotograma lliurat a temps"]
X["JS de 20 ms"] --> KO["Fotograma perdut:<br/>salt visible"]
Traduït a Nómada Tasques, això ja dóna tres objectius concrets abans i tot de mesurar res:
- Prémer «Començar» en una targeta s'ha de veure reflectit en menys de 100 ms.
- El tauler ha de ser visible en menys d'1 s amb una connexió decent.
- Desplaçar la columna de pendents no ha de gastar més de 10 ms de JavaScript per fotograma.
Fixa't en el canvi de llenguatge: hem passat de «va lenta» a tres afirmacions falsables. Això ja és la meitat de la feina.
- Els Web Vitals, un per un
Els llindars de l'apartat anterior són psicologia. Els Web Vitals són la manera estandarditzada de mesurar-los en una pàgina real: un conjunt petit de mètriques definides per Google que capturen càrrega, interactivitat i estabilitat visual. Són les que mesuren Lighthouse, PageSpeed Insights, Search Console i pràcticament qualsevol eina de monitoratge.
N'hi ha tres de principals (els Core Web Vitals) i dues de complementàries que serveixen per diagnosticar.
| Mètrica | Què mesura | Bo | Millorable | Dolent | Què l'empitjora típicament |
|---|---|---|---|---|---|
| LCP (Largest Contentful Paint) | Quan es pinta l'element de contingut més gran de l'àrea visible | ≤ 2,5 s | 2,5–4 s | > 4 s | Servidor lent, CSS i JS que bloquegen, imatges pesades o sense prioritzar, fonts |
| INP (Interaction to Next Paint) | Latència de les interaccions: des de la pulsació fins al pintat següent | ≤ 200 ms | 200–500 ms | > 500 ms | Gestors pesats, tasques llargues, render complet a cada esdeveniment |
| CLS (Cumulative Layout Shift) | Quant es mou el contingut sense que l'usuari ho provoqui | ≤ 0,1 | 0,1–0,25 | > 0,25 | Imatges sense dimensions, fonts que canvien de mida, bàners inserits |
| TTFB (Time to First Byte) | Quan arriba el primer byte del document | ≤ 800 ms | 0,8–1,8 s | > 1,8 s | Servidor, base de dades, redireccions, manca de memòria cau o CDN |
| FCP (First Contentful Paint) | Quan apareix el primer contingut (qualsevol) | ≤ 1,8 s | 1,8–3 s | > 3 s | Recursos que bloquegen el renderitzat, sobretot CSS |
Quatre precisions que eviten la majoria dels malentesos:
LCP no és «quan carrega la pàgina». És quan apareix l'element més gran de l'àrea visible: normalment la imatge de capçalera, un bloc de text gran o —a Nómada Tasques— el contenidor .tauler amb les primeres targetes. Si la teva aplicació pinta un esquelet gris rapidíssim i les targetes reals triguen tres segons, l'LCP serà dolent, i amb raó: l'esquelet no és contingut.
INP va substituir l'FID. La mètrica antiga, First Input Delay, mesurava només el retard fins que començava a executar-se el gestor de la primera interacció; era massa benèvola. INP mesura totes les interaccions de la sessió, de principi a fi —retard d'entrada, execució del gestor i pintat del resultat— i es queda amb (aproximadament) la pitjor. És la mètrica que Nómada Tasques suspendrà: escriure al cercador dispara un render complet de 600 targetes.
CLS no té unitat. És un producte de la fracció de pantalla afectada per la distància recorreguda, acumulat en finestres de sessió. Un 0,21 significa «el contingut salta de manera clarament perceptible». A Nómada Tasques el culpable és previsible: les targetes s'insereixen quan arriben les dades i empenyen cap avall el que hi havia.
TTFB i FCP no són objectius, són diagnòstic. No s'optimitzen per si mateixos: es miren per saber on és el problema d'LCP. Si el TTFB és d'1,4 s dels 4,1 s d'LCP, el problema és del servidor i no l'arreglaràs tocant JavaScript.
flowchart LR
A["Petició"] -->|"TTFB"| B["Primer byte"]
B -->|"analitzar HTML,<br/>CSS bloquejant"| C["FCP<br/>primer contingut"]
C -->|"JS, dades,<br/>imatges"| D["LCP<br/>contingut principal"]
D --> E["Interacció<br/>de l'usuari"]
E -->|"INP"| F["Pintat següent"]
Un advertiment important: els Web Vitals mesuren l'experiència, no la qualitat del teu codi. Una pàgina amb un LCP d'1,2 s pot tenir un JavaScript horrible, i una pàgina impecable pot suspendre per culpa d'una font. Són la primera pregunta, no l'última.
- Dades de laboratori i dades de camp
Veuràs dues vegades la mateixa mètrica amb valors completament diferents, i convé entendre per què abans de tornar-te boig.
| Laboratori (lab data) | Camp (field data / RUM) | |
|---|---|---|
| Com s'obté | Executes Lighthouse o el panell Performance a la teva màquina | Reculls mètriques d'usuaris reals en producció |
| Entorn | Controlat, repetible, simulat | Caòtic: mòbils vells, 3G, pestanyes de fons, extensions |
| Avantatge | Reproduïble; es pot posar a CI; permet comparar A/B | És la veritat: el que de fet li passa a la gent |
| Inconvenient | No és la realitat | No reproduïble; arriba amb retard; necessita volum |
| INP | S'estima; ningú no interactua de debò | Es mesura de debò |
| Es resumeix amb | Una execució (o la mediana de diverses) | El percentil 75 de totes les sessions |
Que es faci servir el percentil 75 és la clau de per què no coincideixen. Quan Search Console diu que el teu LCP és de 3,8 s, no diu que l'usuari mitjà esperi 3,8 s: diu que el 25 % de les visites esperen més que això. El teu Lighthouse local pot marcar 1,9 s sense mentir; simplement estàs mesurant el percentil d'un usuari amb fibra, un portàtil potent i la memòria cau calenta.
La regla operativa:
- Camp per saber si hi ha un problema i a qui li passa.
- Laboratori per trobar-ne la causa i per verificar que un canvi l'arregla.
Fer servir només laboratori porta a optimitzar problemes que ningú no té. Fer servir només camp porta a saber que alguna cosa va malament sense poder esbrinar què.
- Lighthouse: llegir l'informe sense obsessionar-se amb la puntuació
Lighthouse està integrat a DevTools (pestanya Lighthouse), a la línia d'ordres (npx lighthouse) i a PageSpeed Insights. Genera un informe de laboratori amb cinc categories; aquí només ens interessa Performance.
# Informe al terminal, útil per a integració contínua
npx lighthouse http://localhost:5173 \
--preset=desktop \
--output=json --output-path=./informes/lh-escriptori.json
# Perfil mòbil (el que importa): CPU 4× més lenta i xarxa lenta simulada
npx lighthouse http://localhost:5173 \
--output=html --output-path=./informes/lh-mobil.htmlPer defecte Lighthouse simula un mòbil de gamma mitjana: estrangula la CPU a 4× més lenta que la teva i aplica una xarxa lenta. Això explica per què la puntuació mòbil sempre surt molt pitjor que la d'escriptori, i per què la mòbil és la que cal mirar.
Ara, la part important: com llegir l'informe.
- Ignora el número gros. La puntuació de 0 a 100 és una mitjana ponderada de les mètriques, comprimida en una corba log-normal. Pujar de 50 a 60 pot ser un canvi enorme o soroll de mesura; i perseguir el 100 porta a decisions absurdes. El que importa són les mètriques individuals.
- Llegeix les mètriques de la part superior, amb els seus colors. Allà hi ha FCP, LCP, TBT, CLS i Speed Index.
- El TBT (Total Blocking Time) és el teu substitut d'INP en laboratori. Suma, de totes les tasques llargues, els mil·lisegons que excedeixen els 50 ms. És l'única cosa que Lighthouse et pot dir sobre interactivitat sense que hi hagi un usuari interactuant. Si el TBT és alt, l'INP de camp serà dolent.
- Baixa a Diagnostics i a les oportunitats. Allà hi ha les accions concretes, amb l'estalvi estimat. Tracta-les com a pistes, no com a ordres: l'«estalvi estimat» és un model, no un mesurament.
- Busca l'element LCP. L'informe l'assenyala explícitament. Saber quin element és canvia completament el diagnòstic.
- Executa'l tres vegades. Lighthouse té una variància notable. Una sola execució no és una dada.
- Executa'l en mode incògnit i sense extensions. Les extensions injecten scripts i falsegen el resultat; és l'error de mesura més habitual.
Un extracte real de l'informe de Nómada Tasques amb 600 tasques (perfil mòbil, tres execucions, mediana):
| Mètrica de Lighthouse | Valor | Veredicte |
|---|---|---|
| First Contentful Paint | 2,3 s | Millorable |
| Largest Contentful Paint | 4,1 s | Dolent |
| Total Blocking Time | 1.240 ms | Dolent |
| Cumulative Layout Shift | 0,21 | Millorable |
| Speed Index | 3,9 s | Millorable |
| Puntuació | 38 | (irrellevant per si sola) |
Aquest TBT de 1.240 ms és el senyal més fort de l'informe: hi ha més d'un segon en què el fil principal està ocupat i la interfície no respon. Lighthouse no et diu per què. Per això hi ha el panell següent.
- El panell Performance, pas a pas
El panell Performance de DevTools és l'eina central del mòdul. Grava tot el que fa el navegador durant un interval i ho presenta en una línia de temps. La primera vegada intimida; amb un procediment fix deixa de fer-ho.
Procediment de gravació
- Obre l'aplicació en una finestra d'incògnit (sense extensions).
- Obre DevTools → pestanya Performance.
- Marca Screenshots i activa l'estrangulament: CPU: 4× slowdown i, si t'interessa la càrrega, Network: Slow 4G.
- Per mesurar la càrrega: prem el botó de recàrrega amb cercle (Start profiling and reload page). Per mesurar una interacció: prem el cercle de gravar, fes la interacció, i atura en acabar.
- Grava poc. Tres segons de gravació són analitzables; trenta, no.
Com llegir el que has gravat, de dalt a baix:
| Zona | Què conté | Per a què la fas servir |
|---|---|---|
| Captures de pantalla | Fotogrames reals | Veure quan apareix el contingut |
| Web Vitals | Marques d'FCP, LCP, DCL, Load | Ancorar les mètriques a la línia de temps |
| Frames | Un rectangle per fotograma; vermell = perdut | Diagnosticar desplaçaments i animacions |
| Main | El fil principal: el diagrama de flames | On hi ha el 90 % de les respostes |
| Network | Cascada de peticions | Veure què bloqueja què |
| Sumari / Bottom-Up / Call Tree | Agregats de la selecció | Quantificar |
El diagrama de flames (flame chart) del carril Main es llegeix així: l'eix horitzontal és el temps; cada barra és una crida a funció; una barra a sota d'una altra és una funció cridada per la de dalt. L'amplada és el temps total (propi + descendents). Els colors tenen un significat fix:
| Color | Categoria |
|---|---|
| Groc | Scripting (el teu JavaScript) |
| Lila | Rendering (càlcul d'estil i layout) |
| Verd | Painting (pintat i composició) |
| Blau | Loading (analitzar HTML i CSS) |
| Gris | Altres / sistema |
Les tasques llargues són el primer que cal buscar. Una tasca és una unitat de feina que el bucle d'esdeveniments (05-07) executa sense interrupció; si dura més de 50 ms es considera llarga i DevTools la marca amb un triangle vermell a la cantonada i una franja vermella ratllada. Mentre dura, el navegador no pot atendre clics ni pintar. Aquesta és la traducció visual exacta del que vas estudiar a 05-07: un bucle pesat congela la interfície, i await no ho arregla.
Bottom-Up davant de Call Tree. Selecciona un interval a la línia de temps i mira les pestanyes de sota:
| Pestanya | Agrupa per | Respon a |
|---|---|---|
| Call Tree | Cadena de crides des de l'arrel | «Què ha provocat aquesta feina?» |
| Bottom-Up | Funció fulla, sumant totes les seves aparicions | «Quina funció consumeix més temps propi?» |
| Event Log | Ordre cronològic d'esdeveniments | «Què ha passat exactament i en quin ordre?» |
La distinció self time / total time és la que més confon al principi. Total inclou el que triga tot el que la funció crida; self és només el temps dins del seu propi cos. render() pot tenir un total de 310 ms i un self de 2 ms: no és que render sigui lenta, és que crida alguna cosa que ho és. Bottom-Up ordenat per self time és la vista que troba el culpable.
En obrir la primera gravació de Nómada Tasques amb 600 tasques, ordenant Bottom-Up per self time, apareix això:
| Funció | Self time | Total | Aparicions |
|---|---|---|---|
pintarTargeta |
186 ms | 218 ms | 600 |
| Recalculate Style | 74 ms | 74 ms | 41 |
| Layout | 63 ms | 63 ms | 38 |
#visibles |
21 ms | 24 ms | 23 |
Tauler.resum |
18 ms | 96 ms | 69 |
Això ja no és «va lenta»: és «600 crides a pintarTargeta costen 186 ms de temps propi, i hi ha 41 recàlculs d'estil on n'hi hauria d'haver un». Les lliçons 09-02 i 09-04 ataquen exactament aquestes files.
- El panell Network i l'estrangulament de xarxa i CPU
Ja vas fer servir Network a 08-01 per depurar peticions. Aquí el fas servir per a una altra cosa: saber quant es descarrega, en quin ordre i què està bloquejant el renderitzat.
Quatre columnes que cal mirar sempre:
| Columna | Què et diu |
|---|---|
| Size | Dos valors: transferit (comprimit) i real. Si coincideixen, no hi ha compressió |
| Time | Durada total, amb el desglossament a la pestanya Timing |
| Priority | Com prioritza el navegador aquest recurs (Highest, High, Low) |
| Waterfall | La cascada: què espera què |
I tres ajustos de la barra superior:
- Disable cache: obligatori per mesurar la primera visita. Sense això mesures la teva memòria cau, no la dels teus usuaris.
- Throttling de xarxa:
Slow 4Gés el perfil realista per a mòbil. - Estrangulament de CPU: és a la pestanya Performance i a Rendering;
4× slowdownaproxima un mòbil de gamma mitjana,6×un de modest.
Al peu del panell, la barra de resum dóna la dada que importa per a 09-05: peticions, kB transferits, kB de recursos i temps de DOMContentLoaded i Load. Per a Nómada Tasques, amb mòduls ES sense empaquetar (05-04), aquesta barra diu avui:
Trenta-una peticions perquè cada mòdul ES és un fitxer (07-05 ja ho va advertir en escriure la llista de precaché). Aquest número és un problema de 09-05, no d'avui; avui només l'anotem.
- Instrumentar el teu propi codi:
performance.now() i la User Timing API
performance.now() i la User Timing APILes eines anteriors mesuren la pàgina. Per mesurar el teu codi —una funció concreta, una fase concreta— cal instrumentar-lo.
8.1 performance.now()
Date.now() retorna mil·lisegons enters des de 1970 i pot saltar enrere si el rellotge del sistema s'ajusta. performance.now() retorna mil·lisegons des que s'ha obert la pàgina, és monòton (mai no retrocedeix) i té resolució de fracció de mil·lisegon (reduïda deliberadament per seguretat: a la pràctica, granularitat de desenes de microsegons).
const inici = performance.now();
vista.render();
console.log(`render: ${(performance.now() - inici).toFixed(1)} ms`);8.2 performance.mark i performance.measure
console.time/console.timeEnd serveixen per fer una ullada ràpida, però només surten per consola. La User Timing API fa una cosa molt millor: les mesures apareixen al panell Performance, en un carril propi (Timings), alineades amb el diagrama de flames.
// js/util/mesura.js
/** Marca l'inici d'una fase amb nom. */
export function inici(nom) {
performance.mark(`${nom}:inici`);
}
/**
* Tanca la fase, crea la mesura (visible al panell Performance)
* i en retorna la durada en mil·lisegons.
*/
export function fi(nom) {
performance.mark(`${nom}:fi`);
const mesura = performance.measure(nom, `${nom}:inici`, `${nom}:fi`);
// Neteja: si no esborres les marques, el búfer creix durant tota la sessió
performance.clearMarks(`${nom}:inici`);
performance.clearMarks(`${nom}:fi`);
performance.clearMeasures(nom);
return mesura.duration;
}
/** Embolcalla una funció per mesurar-la sense embrutar-ne el cos. */
export function mesurat(nom, fn) {
return function (...args) {
inici(nom);
try {
return fn.apply(this, args);
} finally {
console.log(`${nom}: ${fi(nom).toFixed(1)} ms`); // finally: mesura encara que llanci
}
};
}Tres detalls del codi que convé entendre:
performance.measureretorna l'objectePerformanceMeasuredes de fa anys, així que no cal buscar-lo ambgetEntriesByName.- El
finallygaranteix que el mesurament es tanca encara que la funció llanci una excepció; sense ell, una marca òrfena trenca la mesura següent. - La neteja importa: el búfer d'entrades de rendiment té una mida limitada i les marques velles desplacen les noves.
Instrumentant TaulerVista.render amb això i gravant amb el panell obert, el carril Timings mostra una barra render de 310 ms perfectament alineada amb el bloc groc del fil principal. Ja no cal endevinar quina part del diagrama de flames és teva.
// js/vista/tauler-vista.js — instrumentació temporal
import { inici, fi } from '../util/mesura.js';
render() {
inici('render');
const visibles = this.#visibles();
inici('render:columnes');
// …reconciliar les tres columnes…
fi('render:columnes');
inici('render:resum');
const r = this.#estat.tauler.resum(this.#estat.avui);
// …
fi('render:resum');
fi('render');
}Amb fases imbricades obtens el desglossament sense haver d'interpretar el diagrama de flames:
| Fase | Durada (mediana de 9) |
|---|---|
render |
310 ms |
├─ render:columnes |
291 ms |
└─ render:resum |
4,2 ms |
Deixa la instrumentació fora de producció o darrere d'una bandera. Les marques costen poc, però els console.log dins de bucles costen moltíssim, i a més falsegen el mesurament mateix.
- Mesurar en producció:
PerformanceObserver i web-vitals
PerformanceObserver i web-vitalsPer a la dada de camp cal recollir mètriques d'usuaris reals. L'API base és PerformanceObserver, que notifica entrades de rendiment a mesura que es produeixen.
// js/util/vitals.js — versió artesanal, per entendre el mecanisme
// Tasques llargues: qualsevol bloc de més de 50 ms sense cedir el fil
new PerformanceObserver((llista) => {
for (const entrada of llista.getEntries()) {
console.warn(`Tasca llarga: ${entrada.duration.toFixed(0)} ms`, entrada.attribution);
}
}).observe({ type: 'longtask', buffered: true });
// LCP: arriba diverses vegades; val l'ÚLTIMA abans de la primera interacció
let lcp = 0;
new PerformanceObserver((llista) => {
const entrades = llista.getEntries();
lcp = entrades[entrades.length - 1].startTime;
}).observe({ type: 'largest-contentful-paint', buffered: true });Dos detalls gens obvis:
buffered: truelliura també les entrades que han passat abans de crear l'observador. Sense això et perds l'LCP, que sol produir-se abans que el teu script s'executi.- L'LCP canvia a mesura que es pinta contingut més gran. El valor definitiu és l'últim abans que l'usuari interactuï o la pàgina s'amagui.
Implementar bé LCP, INP i CLS a mà és sorprenentment delicat (finestres de sessió de CLS, agrupació d'esdeveniments d'INP, pàgines restaurades des de la memòria cau de retrocés…). Per a producció es fa servir la llibreria oficial web-vitals, que pesa poc més de 2 kB:
// js/util/vitals.js — versió de producció
import { onLCP, onINP, onCLS, onTTFB, onFCP } from 'web-vitals';
/**
* Envia una mètrica al servidor. `sendBeacon` sobreviu a la descàrrega
* de la pàgina, cosa que un fetch normal no garanteix.
*/
function enviar(metrica) {
const cos = JSON.stringify({
nom: metrica.name, // 'LCP' | 'INP' | 'CLS' | 'TTFB' | 'FCP'
valor: metrica.value,
qualificacio: metrica.rating, // 'good' | 'needs-improvement' | 'poor'
id: metrica.id, // identifica la visita
ruta: location.pathname
});
navigator.sendBeacon?.('/api/vitals', cos)
?? fetch('/api/vitals', { body: cos, method: 'POST', keepalive: true });
}
onLCP(enviar);
onINP(enviar);
onCLS(enviar);
onTTFB(enviar);
onFCP(enviar);Amb això, i agregant al servidor per percentil 75, el Taller Nómada tindria la dada de camp. Recorda: el CLS i l'INP només es coneixen del tot al final de la visita, per això sendBeacon (que l'API de fetch amb keepalive imita) és la manera correcta d'enviar-los.
- Com es fa un benchmark honest
A 07-07 ja vas escriure una funció mesurar per comparar JavaScript amb WebAssembly, i ja vas fer servir dues de les regles: escalfament i mediana. Toca formalitzar-les, perquè la resta del mòdul depèn que els teus mesuraments signifiquin alguna cosa.
| Regla | Per què | Què passa si no la compleixes |
|---|---|---|
| Escalfar abans de mesurar | El JIT (09-02) necessita diverses execucions per optimitzar | La primera execució és 3–10× més lenta: comparació invàlida |
| Repetir (≥ 7 vegades) | Un mesurament és una anècdota | Mesures soroll del sistema |
| Fer servir la mediana, no la mitjana | Una pausa del recol·lector d'escombraries dispara la mitjana | Un valor atípic decideix la teva conclusió |
| Mirar també el mínim | És el cas «sense interferències» | No distingeixes codi lent de màquina ocupada |
| Comparar contra una referència | Els números absoluts no es transfereixen entre màquines | «42 ms» no significa res sense un «davant de què» |
| Mesurar una sola variable | Si canvien dues coses, no saps quina ha actuat | Conclusió no atribuïble |
| Tancar la resta | Altres pestanyes, compilacions, antivirus | Soroll enorme i irregular |
Aquí tens la utilitat completa que farem servir a tot el mòdul:
// js/util/banc.js
/**
* Executa `fn` repetidament i retorna estadístiques robustes.
* @param {string} nom Etiqueta per a l'informe.
* @param {Function} fn Funció a mesurar. Ha de ser autocontinguda.
* @param {object} opcions
* @param {number} opcions.escalfament Execucions descartades (JIT).
* @param {number} opcions.repeticions Execucions mesurades.
*/
export function banc(nom, fn, { escalfament = 5, repeticions = 15 } = {}) {
for (let i = 0; i < escalfament; i += 1) fn(); // 1 · escalfar: no es mesura
const temps = [];
for (let i = 0; i < repeticions; i += 1) { // 2 · mesurar
const t0 = performance.now();
fn();
temps.push(performance.now() - t0);
}
temps.sort((a, b) => a - b); // 3 · estadístiques robustes
const p = (q) => temps[Math.min(temps.length - 1, Math.floor(temps.length * q))];
return {
nom,
min: temps[0],
mediana: p(0.5),
p95: p(0.95),
max: temps.at(-1)
};
}
/** Compara dues alternatives sobre la mateixa entrada i informa de la millora. */
export function comparar(a, b, opcions) {
const ra = banc(a.nom, a.fn, opcions);
const rb = banc(b.nom, b.fn, opcions);
console.table([ra, rb].map((r) => ({
Variant: r.nom,
'Mediana (ms)': r.mediana.toFixed(2),
'Mín (ms)': r.min.toFixed(2),
'p95 (ms)': r.p95.toFixed(2)
})));
console.log(`Millora: ${(ra.mediana / rb.mediana).toFixed(2)}×`);
return { a: ra, b: rb };
}El p95 mereix un comentari: la mediana diu com va normalment, el p95 diu com va en el 5 % de pitjors casos. Per al rendiment percebut, el p95 sovint importa més, perquè és el que produeix l'estrebada que l'usuari recorda.
I una regla d'or que estalvia discussions senceres: si la diferència entre dues variants és menor que la variabilitat del teu propi mesurament, no hi ha diferència. Si la mediana és 12 ms i el rang va de 9 a 18 ms, una «millora» a 11,4 ms és soroll.
- La teva màquina menteix: simular un mòbil modest
El teu portàtil de desenvolupament té un processador ràpid, molta RAM, disc SSD, connexió per cable i la memòria cau calenta. Els teus usuaris, no. La diferència entre un portàtil de desenvolupament i un mòbil de gamma mitjana típic és a l'entorn de 4–6× en CPU d'un sol fil, i encara és més gran en un mòbil de gamma baixa de fa quatre anys.
Traduït: aquests 310 ms de render() que mesures tu són més d'un segon i mig al mòbil des del qual l'Iván consulta el tauler quan és fora del taller.
Com simular-ho:
| Què simular | On | Ajust recomanat |
|---|---|---|
| CPU lenta | Performance → CPU: 4× slowdown | 4× per a gamma mitjana, 6× per a gamma baixa |
| Xarxa lenta | Network → Throttling | Slow 4G |
| Pantalla petita | Device toolbar (Ctrl+Shift+M) | Un mòbil concret de la llista |
| Tot alhora | Lighthouse, perfil mòbil | És el valor per defecte |
Dos advertiments honestos sobre l'estrangulament de CPU: és una simulació (alenteix l'execució de manera uniforme, mentre que un mòbil real també té menys memòria cau, memòria més lenta i limitació tèrmica), i no alenteix la xarxa ni el disc. És una aproximació útil, no una veritat. Si el teu producte és important, prova en un dispositiu real de gamma mitjana amb depuració remota; és l'única mesura que ningú no discuteix.
I la conseqüència metodològica: totes les xifres d'aquest mòdul estan mesurades en una màquina concreta —un portàtil de desenvolupament amb estrangulament de CPU 4× i xarxa Slow 4G, Chrome en mode incògnit— i serveixen per il·lustrar proporcions i ordres de magnitud, no com a valors universals. Al teu equip sortiran altres números. El que no canviarà és la conclusió: 600 crides a pintarTargeta costen tres ordres de magnitud més que un sort.
- La línia base de Nómada Tasques amb 600 tasques
Ja tenim mètode i eines. Establirem el número de partida de tot el mòdul.
Primer, les dades. Necessitem un tauler de 600 tasques realista i reproduïble: si cada execució genera dades diferents, els mesuraments no són comparables. Un generador amb llavor fixa ho resol.
// proves/ajudes/backlog-gran.js
import { Tasca } from '../../js/model/tasca.js';
const RESPONSABLES = ['Iván', 'Lucía', 'Marta'];
const PRIORITATS = ['alta', 'mitjana', 'baixa'];
const ESTATS = ['pendent', 'en-curs', 'feta'];
const ETIQUETES = ['espai', 'serigrafia', 'web', 'fusteria', 'enquadernació', 'compres'];
/** Generador congruencial lineal: pseudoaleatori però REPRODUÏBLE. */
function aleatoriAmbLlavor(llavor) {
let s = llavor;
return () => {
s = (s * 1664525 + 1013904223) % 4294967296;
return s / 4294967296;
};
}
/**
* Dos anys de Taller Nómada: 600 tasques amb la mateixa forma que el backlog
* canònic de 6, perquè el model no noti la diferència.
*/
export function crearBacklogGran(n = 600, llavor = 20260920) {
const r = aleatoriAmbLlavor(llavor);
const tasques = [];
for (let i = 1; i <= n; i += 1) {
const dia = 1 + Math.floor(r() * 27);
const mes = 1 + Math.floor(r() * 12);
tasques.push(new Tasca({
id: i,
titol: `Tasca ${i} · ${ETIQUETES[Math.floor(r() * ETIQUETES.length)]}`,
responsable: RESPONSABLES[Math.floor(r() * 3)],
prioritat: PRIORITATS[Math.floor(r() * 3)],
estat: ESTATS[Math.floor(r() * 3)],
etiquetes: [ETIQUETES[Math.floor(r() * ETIQUETES.length)]],
horesEstimades: 1 + Math.floor(r() * 16),
dataLimit: `2026-${String(mes).padStart(2, '0')}-${String(dia).padStart(2, '0')}`,
revisor: r() > 0.5 ? RESPONSABLES[Math.floor(r() * 3)] : null
}));
}
return tasques;
}Amb la mateixa llavor, sempre les mateixes 600 tasques. Això és el que permet comparar el mesurament d'avui amb el de d'aquí a quatre lliçons.
I aquesta és la línia base, mesurada amb el procediment de l'apartat 10 (mediana de 15 repeticions després de 5 d'escalfament, CPU 4×, Slow 4G, incògnit, al portàtil de referència):
| # | Mesura | Com s'obté | Línia base | Objectiu | Lliçó que l'ataca |
|---|---|---|---|---|---|
| 1 | LCP (mòbil simulat) | Lighthouse mòbil | 4,1 s | ≤ 2,5 s | 09-05 |
| 2 | INP en escriure al cercador | Performance, Interactions | 480 ms | ≤ 200 ms | 09-02, 09-04 |
| 3 | CLS | Lighthouse mòbil | 0,21 | ≤ 0,1 | 09-05 |
| 4 | Tasca llarga màxima a l'arrencada | Performance, Main | 1.180 ms | ≤ 200 ms | 09-02 |
| 5 | render() complet, 600 tasques |
User Timing | 310 ms | ≤ 50 ms | 09-04 |
| 6 | Recàlculs de resum() per filtratge |
User Timing | 96 ms | ≤ 5 ms | 09-02 |
| 7 | Informe de planificació (bloqueja el fil) | User Timing | 940 ms | 0 ms al fil principal | 09-02 |
| 8 | Memòria retinguda després de 200 filtratges | Memory, 3 instantànies | +37,7 MB | ≈ 0 | 09-03 |
| 9 | Nodes DOM del document | Performance, comptador | 7.812 | ≤ 1.500 | 09-04 |
| 10 | JS descarregat abans de la 1a targeta | Network | 214 kB / 28 peticions | ≤ 80 kB / ≤ 5 | 09-05 |
Deu números. Cap opinió. Aquesta taula és el contracte del mòdul: l'última lliçó la repetirà amb la columna «després».
Fixa't que la taula també conté un diagnòstic implícit: hi ha quatre problemes diferents —codi que triga, memòria que es reté, DOM que es maltracta i descàrrega excessiva— i cadascun té la seva lliçó. Això no se sabia abans de mesurar; era perfectament possible que tot fos un únic problema de xarxa.
- El pressupost de rendiment i la integració contínua
Un mesurament puntual es degrada. D'aquí a tres mesos algú afegirà una llibreria de gràfics de 90 kB i ningú no se n'adonarà fins que un usuari es queixi. Un pressupost de rendiment és un conjunt de llindars que, en superar-se, trenquen la compilació: exactament igual que una prova de Jest que falla a 08-03.
Hi ha tres tipus de pressupost i convé tenir-ne dels tres:
| Tipus | Exemple | Eina |
|---|---|---|
| De quantitat | ≤ 80 kB de JS inicial; ≤ 5 peticions | Empaquetador (09-05) |
| De temps | LCP ≤ 2,5 s; TBT ≤ 200 ms | Lighthouse CI |
| De regla | Totes les imatges amb width/height |
Auditoria de Lighthouse |
Lighthouse CI fa el del mig amb molt poca configuració:
{
"ci": {
"collect": {
"url": ["http://localhost:4173/"],
"numberOfRuns": 3,
"startServerCommand": "npm run preview"
},
"assert": {
"assertions": {
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"total-blocking-time": ["error", { "maxNumericValue": 200 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"first-contentful-paint": ["warn", { "maxNumericValue": 1800 }],
"uses-responsive-images": "off"
}
},
"upload": { "target": "temporary-public-storage" }
}
}Tres decisions d'aquest fitxer que no són cosmètiques:
numberOfRuns: 3. Aplica la regla de repetir; Lighthouse CI es queda amb la mediana.errordavant dewarn. Només allò que estàs disposat a bloquejar va com aerror. Un pressupost que trenca la compilació cada dia acaba desactivat."uses-responsive-images": "off". Desactivar explícitament el que no s'aplica és millor que ignorar-ho: en deixa constància de la decisió.
I el pas al flux de treball de GitHub Actions, seguint el mateix ordre barat-primer de 08-06:
# .github/workflows/ci.yml (fragment)
rendiment:
needs: [proves] # el que és barat primer: unitàries, després això
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci
- run: npm run build
- run: npx lhci autorun # retorna codi 1 si s'incompleix el pressupostDos advertiments importants sobre mesurar rendiment a CI. El primer: els executors compartits són sorollosos; els seus temps varien molt més que els de la teva màquina, així que posa els llindars amb marge i desconfia d'una regressió d'un 5 %. El segon: els pressupostos de quantitat (kilobytes, nombre de peticions, nombre de mòduls) són molt més estables que els de temps, i per això són la primera línia de defensa. Els veuràs aplicats a 09-05.
Errors Habituals i Consells
- Optimitzar sense mesurar. És l'error mare. Produeix codi més complicat, de vegades més lent, i sempre més difícil de mantenir.
- Mesurar una sola vegada. Un mesurament és una anècdota. Mínim set repeticions, i mediana.
- Fer servir la mitjana en lloc de la mediana. Una sola pausa del recol·lector d'escombraries la dispara i et porta a una conclusió falsa.
- Mesurar sense escalfament. La primera execució no està optimitzada pel JIT i pot ser deu vegades més lenta.
- Mesurar amb extensions actives. Injecten scripts i observadors. Mesura sempre en incògnit.
- Mesurar amb la memòria cau calenta. Sense Disable cache estàs mesurant la teva segona visita, no la primera del teu usuari.
- Mesurar només al teu portàtil. Estrangula la CPU almenys 4×, o estaràs optimitzant per a tu.
- Perseguir el 100 de Lighthouse. La puntuació és una mitjana ponderada comprimida; els últims punts costen moltíssim i no aporten res perceptible.
- Confondre laboratori amb camp. Si el teu Lighthouse diu 1,9 s i Search Console diu 3,8 s, cap dels dos menteix: un és la teva màquina i l'altre el percentil 75 de la realitat.
- Optimitzar el TTFB tocant JavaScript. El TTFB és del servidor. Diagnostica abans d'actuar.
- Canviar cinc coses alhora. Si millora, no sabràs quina ha servit; si empitjora, tampoc.
- Deixar els
console.logd'instrumentació en producció. Costen, i dins d'un bucle costen moltíssim. - Consell: guarda les gravacions. El panell Performance permet exportar la traça com a
.json. Guarda la de l'«abans»: és l'única manera de comparar de debò. - Consell: mira sempre el self time a Bottom-Up. El total time assenyala qui crida; el self time assenyala el culpable.
- Consell: escriu l'objectiu abans de mesurar. «Que el filtratge respongui en menys de 200 ms» és una fita; «que vagi més ràpid» no.
- Consell: si una optimització no millora el mesurament, reverteix. Complexitat sense benefici és deute pur.
Exercicis
Exercici 1 — El diagnòstic de tres frases. La Marta diu «va lenta», l'Iván «en escriure s'encalla» i la Lucía «triga a carregar». Per a cada frase, indica: (a) quin Web Vital la captura, (b) quina eina faries servir per confirmar-la, (c) quin ajust d'estrangulament aplicaries i (d) quina mesura concreta de la taula de línia base hi correspon. Després explica per què les tres frases no es poden arreglar amb el mateix canvi.
Exercici 2 — Un banc de proves honest. Fent servir banc() de l'apartat 10, escriu un script que compari dues maneres de calcular les hores obertes de les 600 tasques: (a) tasques.filter(t => t.estaOberta()).reduce((s, t) => s + t.horesEstimades, 0) i (b) un únic for amb acumulador. Executa amb 5 d'escalfament i 15 repeticions, i respon amb honestedat: hi ha diferència significativa? Justifica la resposta amb els números de mediana i p95, i digues quina versió posaries al codi.
Exercici 3 — Pressupost per al cercador. L'INP en escriure al cercador és de 480 ms. Escriu (a) l'objectiu numèric i la seva justificació perceptiva, (b) el fragment d'instrumentació amb performance.mark/measure que mesuraria exactament el trajecte «pulsació de tecla → tauler repintat», i (c) una asserció de Lighthouse CI que trenqui la compilació si la interactivitat es degrada. Explica per què l'asserció no pot fer servir directament l'INP.
Solucions
Solució 1
| Frase | (a) Web Vital | (b) Eina | (c) Estrangulament | (d) Mesura |
|---|---|---|---|---|
| «Va lenta» (Marta) | Cap de concret: és un símptoma agregat. Es descompon en LCP + INP | Panell Performance, gravació d'una sessió d'ús | CPU 4× | És la suma de les mesures 4, 5 i 9 |
| «En escriure s'encalla» (Iván) | INP | Performance, carril Interactions; en laboratori, TBT | CPU 4× (la xarxa no hi intervé) | Mesures 2 i 6 |
| «Triga a carregar» (Lucía) | LCP (amb TTFB i FCP com a diagnòstic) | Lighthouse mòbil + panell Network | CPU 4× i xarxa Slow 4G |
Mesures 1 i 10 |
No es poden arreglar amb el mateix canvi perquè passen en moments diferents i tenen causes diferents: la de la Lucía passa abans que s'executi una línia del teu codi de render —és un problema de quant es descarrega i en quin ordre (09-05)—, mentre que la de l'Iván passa amb tot ja carregat i és pura feina del fil principal (09-02 i 09-04). Optimitzar el paquet no arreglarà l'encallada en escriure, i trossejar el render no farà que la primera càrrega sigui més ràpida. La de la Marta és la percepció agregada de les altres dues i desapareixerà quan desapareguin totes dues.
Solució 2
// banc/hores-obertes.js
import { Tauler } from '../js/model/tauler.js';
import { crearBacklogGran } from '../proves/ajudes/backlog-gran.js';
import { comparar } from '../js/util/banc.js';
const tasques = crearBacklogGran(600);
const ambCadena = () =>
tasques.filter((t) => t.estaOberta()).reduce((s, t) => s + t.horesEstimades, 0);
const ambBucle = () => {
let suma = 0;
for (let i = 0; i < tasques.length; i += 1) {
if (tasques[i].estaOberta()) suma += tasques[i].horesEstimades;
}
return suma;
};
console.assert(ambCadena() === ambBucle(), 'les dues versions han de donar el mateix');
comparar({ nom: 'filter+reduce', fn: ambCadena }, { nom: 'for', fn: ambBucle },
{ escalfament: 5, repeticions: 15 });Resultat al portàtil de referència (sense estrangulament, perquè aquí comparem codi, no experiència):
| Variant | Mediana (ms) | Mín (ms) | p95 (ms) |
|---|---|---|---|
filter+reduce |
0,061 | 0,052 | 0,094 |
for |
0,038 | 0,031 | 0,071 |
No hi ha diferència significativa a efectes pràctics. Sí, el for és 1,6× més ràpid en la mediana, però la diferència absoluta és de 0,023 ms: vint microsegons, davant dels 310 ms que costa el render de la mateixa pantalla. És 13.000 vegades menys que el problema real. A més, el rang dels mesuraments (0,052–0,094 davant de 0,031–0,071) se solapa parcialment, senyal que hi ha soroll del mateix ordre que l'efecte.
Jo posaria filter/reduce al codi: es llegeix millor, expressa la intenció i el seu cost és irrellevant. Aquest exercici és, precisament, la demostració experimental de per què el mite «for és més ràpid que els mètodes d'array» no ha de guiar decisions de disseny. Hi tornarem a 09-02 amb la taula completa de mites.
Solució 3
(a) Objectiu: INP ≤ 200 ms, i dins d'això el trajecte tecla → repintat ≤ 100 ms. La justificació és el llindar perceptiu de l'apartat 2: per sota de ~100 ms la resposta se sent instantània; 200 ms és el límit que la mètrica INP considera «bo» perquè inclou també el retard d'entrada i el pintat, que no controles del tot.
(b) Instrumentació. La clau és que la mesura no pot acabar quan acaba render(): ha d'acabar quan el navegador ha pintat. Això s'aconsegueix amb un requestAnimationFrame imbricat (07-06): el primer s'executa abans del pintat, el segon just després.
// js/vista/controlador.js — instrumentació del cercador
import { inici, fi } from '../util/mesura.js';
campCerca.addEventListener('input', (esdeveniment) => {
inici('cercador');
vista.actualitzar({ filtres: { text: esdeveniment.target.value } });
// El pintat passa DESPRÉS del gestor; cal esperar dos fotogrames
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const ms = fi('cercador');
if (ms > 100) console.warn(`Cercador lent: ${ms.toFixed(0)} ms`);
});
});
});(c) Asserció de CI:
"total-blocking-time": ["error", { "maxNumericValue": 200 }],
"max-potential-fid": ["warn", { "maxNumericValue": 130 }]L'INP no es pot fer servir directament perquè és una mètrica de camp: necessita que un usuari real interactuï, i a Lighthouse CI ningú no interactua. El que sí que es pot vigilar és el TBT, que mesura el temps total en què el fil principal està bloquejat i que correlaciona bé amb un INP dolent: si no hi ha tasques llargues, no hi ha manera que una interacció trigui 480 ms. Per vigilar l'INP de debò cal el web-vitals de l'apartat 9 enviant dades des de producció, amb alerta si el percentil 75 supera els 200 ms.
Conclusió
Has canviat el punt de partida de tot el mòdul. «Nómada Tasques va lenta» ja no és una frase: és una taula de deu números mesurats amb un procediment repetible sobre un tauler de 600 tasques generat amb llavor fixa.
Saps per què falla la intuïció i què deia Knuth de debò: no «no optimitzis», sinó «no optimitzis allò que no has mesurat, perquè les suposicions intuïtives dels programadors fallen». Coneixes els llindars que defineixen «ràpid» per a una persona —100 ms per sentir resposta, 1 s per no perdre el fil, 16,7 ms per fotograma a 60 Hz, amb menys de 10 ms de JavaScript a dins— i saps traduir-los a objectius falsables.
Domines els Web Vitals: LCP (2,5 s) com a mesura de quan apareix el contingut principal, INP (200 ms) com a mesura de totes les interaccions de la sessió —no només la primera, com l'antic FID—, CLS (0,1) com a mesura d'estabilitat visual, i TTFB i FCP com a instruments de diagnòstic que diuen on és el problema d'LCP. I entens per què el teu Lighthouse local i les dades de camp no coincideixen mai: un és la teva màquina amb la memòria cau calenta, l'altre el percentil 75 de mòbils reals en xarxes reals.
Tens l'instrumental complet: Lighthouse llegit per mètriques i no per puntuació, amb el TBT com a substitut de laboratori de l'INP; el panell Performance amb el seu procediment fix —incògnit, estrangular, gravar poc— i la seva lectura per capes, amb les tasques llargues marcades en vermell i la distinció entre Call Tree (qui ho ha provocat) i Bottom-Up ordenat per self time (qui ho consumeix); el panell Network amb Disable cache i Slow 4G; la User Timing API amb performance.mark/measure perquè les teves pròpies fases apareguin a la línia de temps; i PerformanceObserver amb la llibreria web-vitals i sendBeacon per a la dada de camp. I saps fer un benchmark honest: escalfar, repetir, mediana i p95, comparar contra una referència, canviar una sola variable, i descartar com a soroll tota diferència menor que la variabilitat del mesurament mateix.
Sobretot, tens el contracte del mòdul: 4,1 s d'LCP, 480 ms d'INP, 0,21 de CLS, una tasca llarga de 1.180 ms, 310 ms de render, 96 ms de recàlculs, 940 ms d'informe bloquejant, 37,7 MB retinguts, 7.812 nodes i 214 kB en 28 peticions. Amb el seu pressupost de rendiment corresponent i un lhci autorun que trenca la integració contínua quan algú l'incompleix, perquè un pressupost que ningú no vigila deixa d'existir en tres mesos.
La taula també reparteix la feina. Les files 2, 4, 6 i 7 —INP, tasca llarga, recàlculs i l'informe de 940 ms que congela la pantalla— són codi teu que triga massa a executar-se: algorismes amb el cost equivocat, càlculs repetits que es podrien fer una sola vegada, gestors que es disparen seixanta vegades per segon i una feina pesada que s'obstina a ocupar l'únic fil que pinta la interfície. Allà hi ha la major part del segon perdut, i allà comença la lliçó següent: Optimització del Rendiment de JavaScript, on veuràs com treballa el motor per dins just el necessari per no sabotejar-lo, canviaràs una cerca quadràtica per un índex amb Map, posaràs una memòria cau ben invalidada a Tauler, aprendràs quan toca debounce i quan throttle, trossejaràs la feina llarga per deixar respirar el bucle d'esdeveniments i, finalment, trauràs l'informe de planificació del fil principal ficant-lo en un Web Worker. Tot això amb la mateixa disciplina: mesurament abans, mesurament després, i revertir el que no millori.
Curs de JavaScript: De Principiant a Avançat
Mòdul 1: Introducció a JavaScript
- Què és JavaScript?
- Configuració del teu Entorn de Desenvolupament
- El teu Primer Programa en JavaScript
- Sintaxi i Conceptes Bàsics de JavaScript
- Variables i Tipus de Dades
- Operadors Bàsics
- Conversió de Tipus i Comparacions
- El Projecte del Curs: Nómada Tasques
Mòdul 2: Estructures de Control
- Sentències Condicionals
- Bucles: for, while, do-while
- Sentències Switch
- Control del Flux: break, continue i Bucles Imbricats
- Gestió d'Errors amb try-catch
Mòdul 3: Funcions
- Definició i Crida de Funcions
- Expressions de Funció i Funcions Fletxa
- Paràmetres i Valors de Retorn
- Àmbit i Closures
- Hoisting i el Context d'Execució
- Funcions d'Ordre Superior
- Recursivitat
Mòdul 4: Objectes i Arrays
- Introducció als Objectes
- Mètodes d'Objecte i la Paraula Clau
this - Arrays: Conceptes Bàsics i Mètodes
- Iteració sobre Arrays
- Cercar, Ordenar i Agregar Dades: find, sort i reduce
- Desestructuració d'Arrays
- Desestructuració d'Objectes, Spread i Rest
- JSON i Còpies d'Objectes
Mòdul 5: Objectes i Funcions Avançades
- Prototips i Herència
- Classes i Programació Orientada a Objectes
- Encapsulació: Getters, Setters i Camps Privats
- Mòduls i Importació/Exportació
- JavaScript Asíncron: Callbacks
- Promeses i Async/Await
- El Bucle d'Esdeveniments i la Cua de Microtasques
- Iteradors i Generadors
Mòdul 6: El Model d'Objectes del Document (DOM)
- Introducció al DOM
- Selecció i Manipulació d'Elements del DOM
- Gestió d'Esdeveniments
- Propagació, Delegació i Esdeveniments Personalitzats
- Creació i Eliminació d'Elements del DOM
- Renderitzat de Llistes i Plantilles HTML
- Gestió i Validació de Formularis
Mòdul 7: APIs del Navegador i Temes Avançats
- Emmagatzematge Local i de Sessió
- Fetch API i AJAX
- Peticions Robustes: Errors, Timeouts i AbortController
- WebSockets
- Service Workers i Aplicacions Web Progressives (PWAs)
- APIs del Navegador Essencials
- Introducció a WebAssembly
Mòdul 8: Proves i Depuració
- Depuració de JavaScript
- Qualitat de Codi: ESLint, Prettier i Convencions
- Proves Unitàries amb Jest
- Dobles de Prova: Mocks, Stubs i Spies
- Proves d'Integració
- Proves d'Extrem a Extrem amb Cypress
Mòdul 9: Rendiment i Optimització
- Mesurar Abans d'Optimitzar: DevTools i Web Vitals
- Optimització del Rendiment de JavaScript
- Gestió de Memòria
- Manipulació Eficient del DOM
- Càrrega Diferida i Divisió de Codi
Mòdul 10: Frameworks i Llibreries de JavaScript
- Per Què Existeixen els Frameworks
- Introducció a React
- Gestió d'Estat amb Redux
- Conceptes Bàsics de Vue.js
- Conceptes Bàsics d'Angular
- Triar el Framework Adequat
