Mehr Interaktivität mit JavaScript

Ilya Grigorik
Ilya Grigorik

Veröffentlicht: 31. Dezember 2013

Mit JavaScript können wir nahezu jeden Aspekt der Seite anpassen: Inhalt, Gestaltung 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 Rendering-Pfad entfernen.

  • JavaScript kann das DOM und das CSSOM abfragen und ändern.
  • Die JavaScript-Ausführung wird im CSSOM blockiert.
  • JavaScript blockiert die DOM-Erstellung, sofern es nicht ausdrücklich als asynchron deklariert wird.

JavaScript ist eine dynamische Sprache, die in einem Browser ausgeführt wird und es uns ermöglicht, nahezu jeden Aspekt des Seitenverhaltens zu ändern: Wir können Inhalte ändern, indem wir Elemente zum DOM-Baum hinzufügen oder daraus entfernen, wir können die CSSOM-Eigenschaften der einzelnen Elemente ä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 dann über die Referenz verfügen, können wir ihren Text ändern (über .textContent) und sogar die berechnete Darstellungsstileigenschaft von "none" in "inline" überschreiben. Jetzt wird auf unserer Seite Hallo interaktive Schüler angezeigt.

  • Mit JavaScript können wir außerdem neue Elemente im DOM erstellen, gestalten, 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 auf ein Script-Tag stößt, wird der Aufbau des DOM pausiert und die Kontrolle an die JavaScript-Engine übergeben. 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. 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 gerade gut für die Leistung: Der Browser verzögert die Skriptausführung und die DOM-Erstellung, bis der Download und die Erstellung des CSSOM abgeschlossen sind.

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.
  • JavaScript kann das DOM und das CSSOM abfragen und ändern.
  • 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. Wir haben dies in unserem Beispiel mit einem Inline-Skript in Aktion 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? Im vorherigen Beispiel wird der Code in eine separate Datei extrahiert:

<!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 durch den Parser 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 Attribut async 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