Aggiungere interattività con JavaScript

Ilya Grigorik
Ilya Grigorik

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 non necessario 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 dichiarata esplicitamente come asincrona.

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>

Prova

  • JavaScript ci consente di accedere al DOM ed estrarre il riferimento al nodo span nascosto; il nodo potrebbe non essere visibile nella struttura ad albero 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 un unico file JavaScript di grandi dimensioni che crea e applica stili agli elementi uno per uno. Anche se funziona, in pratica è molto più semplice 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.

Un&#39;anteprima di una pagina visualizzata su un dispositivo mobile.

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, 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. 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. 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 race condition.

Cosa succede se il browser non ha ancora 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 del browser:

  • 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 fino al momento in cui il CSSOM è 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 del parser": 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 in linea bloccano sempre il parser, a meno che non scrivi codice aggiuntivo per posticipare la loro esecuzione.

Che dire degli 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);

Prova

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 mettere in pausa per attendere il recupero dello script dal disco, dalla cache o da un server remoto, il che può aggiungere da decine a migliaia di millisecondi di ritardo al percorso di rendering critico.

Per impostazione predefinita, tutto il codice JavaScript è bloccato dal 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 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>

Prova

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.

Feedback