Data di pubblicazione: 31 dicembre 2013
JavaScript ci consente di modificare quasi ogni aspetto della pagina: contenuti, stile e risposta all'interazione dell'utente. Tuttavia, JavaScript può anche bloccare la costruzione del DOM e ritardare il rendering della pagina. Per ottenere prestazioni ottimali, imposta JavaScript come asincrono ed elimina qualsiasi codice JavaScript superfluo dal percorso di rendering critico.
Riepilogo
- JavaScript può eseguire query e modificare il DOM e il CSSOM.
- L'esecuzione di JavaScript si blocca nel CSSOM.
- JavaScript blocca la costruzione del DOM, a meno che non sia dichiarato esplicitamente come asincrono.
JavaScript è un linguaggio dinamico che viene eseguito in un browser e ci consente di modificare quasi ogni aspetto del comportamento della pagina: possiamo modificare i contenuti aggiungendo e rimuovendo elementi dall'albero DOM; possiamo modificare le proprietà CSSOM di ogni elemento; possiamo gestire l'input dell'utente e molto altro ancora. Per esemplificare, vediamo cosa succede quando l'esempio precedente "Hello World" viene modificato per aggiungere un breve script in linea:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
JavaScript ci consente di accedere al DOM ed estrarre il riferimento al nodo span nascosto; il nodo potrebbe non essere visibile nella struttura di rendering, ma è comunque presente nel DOM. Una volta ottenuto il riferimento, possiamo modificare il testo (tramite .textContent) e persino sostituire la proprietà dello stile di visualizzazione calcolato da "none" a "in linea". Ora la nostra pagina mostra "Un saluto agli studenti interattivi!".
JavaScript ci consente inoltre di creare, applicare stili, aggiungere e rimuovere nuovi elementi nel DOM. Tecnicamente, l'intera pagina potrebbe essere solo un unico grande file JavaScript che crea e applica gli stili agli elementi uno alla volta. Anche se funziona, in pratica è molto più facile utilizzare HTML e CSS. Nella seconda parte della nostra funzione JavaScript creiamo un nuovo elemento div, ne impostiamo il contenuto di testo, lo stilizziamo e lo aggiungiamo al corpo.
In questo modo, abbiamo modificato i contenuti e lo stile CSS di un nodo DOM esistente e abbiamo aggiunto un nodo completamente nuovo al documento. La nostra pagina non vincerà premi di design, ma illustra la potenza e la flessibilità di JavaScript.
Tuttavia, sebbene JavaScript offra molte funzionalità, crea molte limitazioni aggiuntive su come e quando viene eseguito il rendering della pagina.
Innanzitutto, noterai che nell'esempio precedente lo script incorporato si trova in fondo alla pagina. Perché? Beh, dovresti provarlo tu stesso, ma se spostiamo lo script sopra l'elemento <span>
, noterai che lo script non va a buon fine e segnala che non riesce a trovare un riferimento a elementi <span>
nel documento, ovvero getElementsByTagName('span')
restituisce null
. Questo dimostra una proprietà importante: il nostro script viene eseguito nel punto esatto in cui è inserito nel documento. Quando l'interprete HTML incontra un tag script, mette in pausa il processo di creazione del DOM e cede il controllo all'engine JavaScript. Al termine dell'esecuzione dell'engine JavaScript, il browser riprende da dove aveva interrotto e riprende la costruzione del DOM.
In altre parole, il blocco di script non riesce a trovare elementi più avanti nella pagina perché non sono ancora stati elaborati. In altre parole, l'esecuzione del nostro script in linea blocca la costruzione del DOM, il che ritarda anche il rendering iniziale.
Un'altra proprietà sottile dell'introduzione di script nella nostra pagina è che possono leggere e modificare non solo il DOM, ma anche le proprietà CSSOM. Questo è esattamente quello che stiamo facendo nell'esempio, quando modifichiamo la proprietà di visualizzazione dell'elemento span da "Nessuno" a "In linea". Il risultato finale? Ora abbiamo una race condition.
Cosa succede se il browser non ha completato il download e la creazione del CSSOM quando vogliamo eseguire il nostro script? La risposta non è molto buona per il rendimento: il browser ritarda l'esecuzione dello script e la costruzione del DOM fino al termine del download e della costruzione del CSSOM.
In breve, JavaScript introduce molte nuove dipendenze tra il DOM, il CSSOM e l'esecuzione di JavaScript. Ciò può causare ritardi significativi nell'elaborazione e nel rendering della pagina sullo schermo:
- La posizione dello script nel documento è significativa.
- Quando il browser rileva un tag script, la costruzione del DOM viene interrotta fino al termine dell'esecuzione dello script.
- JavaScript può eseguire query e modificare il DOM e il CSSOM.
- L'esecuzione di JavaScript viene messa in pausa finché il CSSOM non è pronto.
In larga misura, "ottimizzazione del percorso di rendering critico" si riferisce alla comprensione e all'ottimizzazione del grafo di dipendenza tra HTML, CSS e JavaScript.
Blocco del parser rispetto a JavaScript asincrono
Per impostazione predefinita, l'esecuzione di JavaScript è "di blocco dell'analizzatore sintattico": quando il browser rileva uno script nel documento, deve mettere in pausa la costruzione del DOM, trasferire il controllo al runtime di JavaScript e consentire l'esecuzione dello script prima di procedere con la costruzione del DOM. Abbiamo visto questo in azione con uno script in linea nel nostro esempio precedente. Infatti, gli script incorporati bloccano sempre i parser, a meno che non venga scritto codice aggiuntivo per posticiparne l'esecuzione.
Cosa accade per gli script inclusi utilizzando un tag script? Prendi l'esempio precedente ed estrai il codice in un file separato:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script External</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
app.js
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
Che tu utilizzi un tag <script> o uno snippet JavaScript in linea, entrambi dovrebbero comportarsi allo stesso modo. In entrambi i casi, il browser mette in pausa ed esegue lo script prima di poter elaborare il resto del documento. Tuttavia, nel caso di un file JavaScript esterno, il browser deve essere messo in pausa per attendere che lo script venga recuperato da un disco, da una cache o da un server remoto, il che può causare un ritardo da decine a migliaia di millisecondi al percorso di rendering critico.
Per impostazione predefinita, tutto il codice JavaScript blocca il blocco dei parser. Dal momento che il browser non sa che cosa ha intenzione di fare lo script sulla pagina, presume lo scenario peggiore e blocca l'analizzatore sintattico. Un indicatore al browser che lo script non deve essere eseguito nel punto esatto in cui viene fatto riferimento consente al browser di continuare a costruire il DOM e di eseguire lo script quando è pronto, ad esempio dopo il recupero del file dalla cache o da un server remoto.
Per farlo, l'attributo async
viene aggiunto all'elemento <script>
:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script Async</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
L'aggiunta della parola chiave async al tag script indica al browser di non bloccare la costruzione del DOM mentre attende che lo script diventi disponibile, il che può migliorare notevolmente le prestazioni.