Data di pubblicazione: 31 dicembre 2013
JavaScript consente di modificare quasi tutti gli aspetti della pagina: contenuti, lo stile e la sua risposta all'interazione dell'utente. Tuttavia, JavaScript può anche bloccare la costruzione del DOM e ritardare il rendering della pagina. Per offrire ai clienti del rendimento, rendi il tuo codice JavaScript asincrono ed elimina il codice JavaScript non necessario dal percorso di rendering critico.
Riepilogo
- JavaScript può eseguire query e modificare il DOM e il CSSOM.
- L'esecuzione di JavaScript blocca in CSSOM.
- JavaScript blocca la costruzione del DOM, a meno che non sia dichiarata esplicitamente come asincrona.
JavaScript è un linguaggio dinamico che viene eseguito in un browser e ci consente di modificare praticamente ogni aspetto del comportamento della pagina: possiamo modificare i contenuti aggiungendo e rimuovendo elementi dalla struttura DOM; possiamo modificare le proprietà CSSOM di ogni elemento; possiamo gestire l'input dell'utente; e molto altro ancora. Per spiegarlo, vediamo cosa succede quando la precedente frase "Hello World" l'esempio seguente viene modificato per aggiungere un breve script incorporato:
<!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. Quando abbiamo il riferimento, possiamo cambiarne il testo (tramite .textContent) e persino sostituire la proprietà di stile di visualizzazione calcolata da "nessuno" su "inline". 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 potrebbe funzionare, in pratica l'utilizzo di HTML e CSS è molto più semplice. 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 ci offra molta potenza, crea molte altre limitazioni su come e quando viene eseguito il rendering della pagina.
Innanzitutto, tieni presente che nell'esempio precedente lo script in linea si trova nella parte inferiore della 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. Oppure, in altre parole, l'esecuzione dello script incorporato blocca la creazione del DOM, ritardando 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. In effetti, è esattamente ciò che stiamo facendo nel nostro esempio quando modifichiamo la proprietà display dell'elemento span da none a inline. Il risultato finale? Ora abbiamo una condizione di gara.
Cosa succede se il browser non ha terminato il download e lo sviluppo del CSSOM quando vogliamo eseguire lo 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. Di conseguenza, il browser potrebbe subire 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 creazione del DOM viene messa in pausa 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.
Confronto tra blocco del parser e JavaScript asincrono
Per impostazione predefinita, l'esecuzione di JavaScript è "blocco del parser": quando il browser rileva uno script nel documento, deve sospendere la creazione del DOM, trasferire il controllo al runtime JavaScript e lasciare che lo script venga eseguito prima di procedere con la creazione del DOM. Abbiamo visto questa pratica con uno script incorporato 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? Considera 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);
Sia che utilizziamo uno <script> o uno snippet JavaScript incorporato, si accetteranno che entrambi si comportino allo stesso modo. In entrambi i casi, il browser mette in pausa ed eseguito lo script prima di poter elaborare il resto del documento. Tuttavia, nel caso di un file JavaScript esterno il browser deve sospendere per attendere che lo script venga recuperato dal disco, dalla cache o da un server remoto, è possibile aggiungere da decine a migliaia di millisecondi di ritardo al rendering critico del tuo percorso di apprendimento.
Per impostazione predefinita, tutto il codice JavaScript blocca il blocco dei parser. Poiché il browser non sa cosa lo script intende fare nella pagina, presuppone lo scenario peggiore e blocca l'analizzatore. 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 ottenere questo risultato, 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 asincrona al tag script indica al browser di non bloccare la creazione del DOM mentre attende che lo script diventi disponibile, il che può migliorare notevolmente le prestazioni.