Mehr Interaktivität mit JavaScript

Ilya Grigorik
Ilya Grigorik

Veröffentlicht: 31. Dezember 2013

Mit JavaScript können wir fast jeden Aspekt der Seite ändern: Inhalt, Stil und Reaktion auf Nutzerinteraktionen. JavaScript kann jedoch auch die DOM-Erstellung blockieren und das Rendern der Seite verzögern. Für eine optimale Leistung sollten Sie Ihr JavaScript asynchron ausführen und unnötiges JavaScript aus dem kritischen Renderingpfad entfernen.

  • Mit JavaScript können das DOM und das CSSOM abgefragt und geändert werden.
  • Die JavaScript-Ausführung wird im CSSOM blockiert.
  • JavaScript blockiert die DOM-Erstellung, es sei denn, sie wird explizit als asynchron deklariert.

JavaScript ist eine dynamische Sprache, die in einem Browser ausgeführt wird und mit der sich nahezu jeder Aspekt des Seitenverhaltens ändern lässt: Wir können Inhalte ändern, indem wir Elemente zum DOM-Baum hinzufügen und daraus entfernen, die CSSOM-Eigenschaften jedes Elements ändern, Nutzereingaben verarbeiten und vieles mehr. Zur Veranschaulichung sehen Sie sich an, was passiert, wenn dem vorherigen „Hello World“-Beispiel ein kurzes Inline-Script hinzugefügt wird:

<!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>

Ausprobieren

  • Mit JavaScript können wir in das DOM eindringen und die Referenz auf den ausgeblendeten Span-Knoten abrufen. Der Knoten ist möglicherweise nicht im Renderbaum sichtbar, aber er ist noch im DOM vorhanden. Wenn wir die Referenz haben, können wir den Text (über .textContent) ändern und sogar die berechnete Eigenschaft „display-style“ von „none“ zu „inline“ überschreiben. Jetzt wird auf unserer Seite Hallo interaktive Schüler angezeigt.

  • Mit JavaScript können wir auch neue Elemente im DOM erstellen, stylen, anhängen und entfernen. Technisch gesehen könnte unsere gesamte Seite nur eine große JavaScript-Datei sein, in der die Elemente einzeln erstellt und formatiert werden. Das würde zwar funktionieren, in der Praxis ist es jedoch viel einfacher, HTML und CSS zu verwenden. Im zweiten Teil unserer JavaScript-Funktion erstellen wir ein neues Div-Element, legen den Textinhalt fest, formatieren es und hängen es an den Body an.

Eine Vorschau einer Seite, die auf einem Mobilgerät gerendert wird.

Damit haben wir den Inhalt und den CSS-Stil eines vorhandenen DOM-Knotens geändert und dem Dokument einen ganz neuen Knoten hinzugefügt. Unsere Seite wird keine Designpreise gewinnen, aber sie veranschaulicht die Leistungsfähigkeit und Flexibilität von JavaScript.

JavaScript bietet zwar viele Möglichkeiten, schränkt aber auch die Möglichkeiten ein, wie und wann die Seite gerendert wird.

Beachten Sie zuerst, dass sich unser Inline-Script im vorherigen Beispiel fast am Ende der Seite befindet. Warum? Sie sollten das selbst ausprobieren. Wenn wir das Script jedoch über das <span>-Element verschieben, wird eine Fehlermeldung ausgegeben, dass im Dokument keine Referenz auf <span>-Elemente gefunden werden kann. Das heißt, getElementsByTagName('span') gibt null zurück. Das zeigt eine wichtige Eigenschaft: Unser Script wird genau an der Stelle ausgeführt, an der es in das Dokument eingefügt wurde. Wenn der HTML-Parser ein Script-Tag findet, hält er den Aufbau des DOM an und übergibt die Kontrolle an die JavaScript-Engine. Nach Abschluss der Ausführung der JavaScript-Engine setzt der Browser dort fort, wo er aufgehört hat, und fährt mit dem DOM-Aufbau fort.

Mit anderen Worten: Unser Scriptblock kann später auf der Seite keine Elemente finden, da sie noch nicht verarbeitet wurden. Oder anders ausgedrückt: Die Ausführung unseres Inline-Scripts blockiert die DOM-Erstellung, was auch das erste Rendern verzögert.

Eine weitere subtile Eigenschaft von Scripts auf unserer Seite ist, dass sie nicht nur das DOM, sondern auch die CSSOM-Properties lesen und ändern können. Genau das tun wir in unserem Beispiel, wenn wir die Displayeigenschaft des span-Elements von „none“ zu „inline“ ändern. Das Ergebnis? Wir haben jetzt eine Race-Bedingung.

Was passiert, wenn der Browser das Herunterladen und Erstellen des CSSOM noch nicht abgeschlossen hat, wenn wir unser Script ausführen möchten? Die Antwort ist nicht sehr leistungsfreundlich: Der Browser verzögert die Scriptausführung und das DOM-Aufbau, bis das CSSOM heruntergeladen und erstellt wurde.

Kurz gesagt: JavaScript führt viele neue Abhängigkeiten zwischen dem DOM, dem CSSOM und der JavaScript-Ausführung ein. Dies kann zu erheblichen Verzögerungen bei der Verarbeitung und dem Rendern der Seite im Browser führen:

  • Der Speicherort des Scripts im Dokument ist wichtig.
  • Wenn der Browser auf ein Script-Tag stößt, wird der DOM-Aufbau pausiert, bis die Ausführung des Scripts abgeschlossen ist.
  • Mit JavaScript können das DOM und das CSSOM abgefragt und geändert werden.
  • Die JavaScript-Ausführung wird pausiert, bis das CSSOM bereit ist.

„Optimierung des kritischen Rendering-Pfads“ bezieht sich in hohem Maße auf das Verständnis und die Optimierung des Abhängigkeitsgraphen zwischen HTML, CSS und JavaScript.

Parser-Blockierung im Vergleich zu asynchronem JavaScript

Standardmäßig wird JavaScript-Code „parser-blockierend“ ausgeführt: Wenn der Browser ein Script im Dokument findet, muss er die DOM-Erstellung pausieren, die Kontrolle an die JavaScript-Laufzeit übergeben und das Script ausführen, bevor er mit der DOM-Erstellung fortfährt. In unserem vorherigen Beispiel haben wir das anhand eines Inline-Scripts gesehen. Inline-Scripts blockieren den Parser immer, es sei denn, Sie schreiben zusätzlichen Code, um ihre Ausführung zu verschieben.

Was ist mit Scripts, die über ein Script-Tag eingebunden sind? Extrahieren Sie den Code aus dem vorherigen Beispiel in eine separate Datei:

<!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);

Ausprobieren

Unabhängig davon, ob wir ein <script>-Tag oder ein Inline-JavaScript-Snippet verwenden, sollten beide dasselbe Verhalten zeigen. In beiden Fällen hält der Browser das Script an und führt es aus, bevor er den Rest des Dokuments verarbeiten kann. Bei einer externen JavaScript-Datei muss der Browser jedoch warten, bis das Script von der Festplatte, dem Cache oder einem Remote-Server abgerufen wurde. Dies kann zu einer Verzögerung von zehn bis tausend Millisekunden beim kritischen Renderingpfad führen.

Standardmäßig wird JavaScript blockiert. Da der Browser nicht weiß, was das Script auf der Seite tun soll, geht er vom Worst-Case-Szenario aus und blockiert den Parser. Wenn dem Browser signalisiert wird, dass das Script nicht genau an der Stelle ausgeführt werden muss, an der es referenziert wird, kann der Browser das DOM weiter erstellen und das Script ausführen, wenn es bereit ist, z. B. nachdem die Datei aus dem Cache oder von einem Remote-Server abgerufen wurde.

Dazu wird dem <script>-Element das async-Attribut hinzugefügt:

<!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>

Ausprobieren

Wenn Sie dem Script-Tag das Keyword „async“ hinzufügen, wird der Browser angewiesen, die DOM-Erstellung nicht zu blockieren, während er auf die Verfügbarkeit des Scripts wartet. Dies kann die Leistung erheblich verbessern.

Feedback