Mehr Interaktivität mit JavaScript

Ilya Grigorik
Ilya Grigorik

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

Zusammenfassung

  • JavaScript kann das DOM und das CSSOM abfragen und ändern.
  • JavaScript-Ausführungsblöcke auf CSSOM.
  • JavaScript blockiert die DOM-Erstellung, sofern es nicht explizit 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 aus dem DOM-Baum entfernen, wir können die CSSOM-Eigenschaften jedes Elements ändern, wir können Benutzereingaben verarbeiten und vieles mehr. Zur Veranschaulichung ergänzen wir unser vorheriges "Hello World"-Beispiel mit einem einfachen Inline-Skript:

<!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 zugreifen und den Verweis auf den ausgeblendeten Span-Knoten abrufen. Der Knoten ist möglicherweise nicht in der Rendering-Baumstruktur sichtbar, er befindet sich jedoch noch im DOM. Wenn wir dann über den Verweis verfügen, können wir seinen Text (über .textContent) ändern und sogar die berechnete Anzeigestileigenschaft von „none“ in „inline“ überschreiben. Auf der Seite wird jetzt Hello Interactive students! angezeigt.

  • Mit JavaScript können Sie auch neue Elemente im DOM erstellen, gestalten, anhängen und entfernen. Technisch gesehen könnte unsere gesamte Seite nur eine große JavaScript-Datei sein, die die Elemente einzeln erstellt und gestaltet. Das würde zwar funktionieren, in der Praxis ist die Verwendung von HTML und CSS jedoch viel einfacher. Im zweiten Teil unserer JavaScript-Funktion erstellen wir ein neues div-Element, legen seinen Textinhalt fest, versehen es mit einem Stil und hängen es an den Textkörper an.

Seitenvorschau

Damit haben wir den Inhalt und den CSS-Stil eines vorhandenen DOM-Knotens geändert und dem Dokument einen völlig neuen Knoten hinzugefügt. Unsere Seite erhält keine Designauszeichnungen, aber sie zeigt die Leistungsfähigkeit und Flexibilität, die uns JavaScript bietet.

JavaScript bietet uns zwar sehr viel Leistung, es gibt jedoch auch viele zusätzliche Einschränkungen dafür, wie und wann die Seite gerendert wird.

Beachten Sie zunächst, dass sich unser Inline-Skript im obigen Beispiel unten auf der Seite befindet. Warum? Sie sollten es selbst ausprobieren. Wenn wir das Skript jedoch oberhalb des span-Elements platzieren, werden Sie feststellen, dass das Skript fehlschlägt und es keinen Verweis auf span-Elemente im Dokument finden kann. Das heißt, getElementsByTagName(‘span') gibt null zurück. Dies stellt eine wichtige Eigenschaft dar: Unser Skript wird genau an dem Punkt ausgeführt, an dem es im Dokument eingefügt wird. Wenn der HTML-Parser auf ein Skript-Tag stößt, unterbricht er die Erstellung des DOMs und gibt die Steuerung an die JavaScript-Engine weiter. Wenn die JavaScript-Engine ausgeführt wurde, fährt der Browser an der Stelle fort, an der er aufgehört hat, und setzt die DOM-Erstellung fort.

Unser Skriptblock kann also später auf der Seite keine Elemente finden, da sie noch nicht verarbeitet wurden. Oder anders ausgedrückt: Durch die Ausführung unseres Inline-Skripts wird die DOM-Erstellung blockiert, was auch das anfängliche Rendering verzögert.

Eine weitere subtile Eigenschaft beim Einbinden von Skripts in unsere Seite besteht darin, dass sie nicht nur das DOM, sondern auch die CSSOM-Eigenschaften lesen und ändern können. Genau das machen wir in unserem Beispiel, wenn wir die Anzeigeeigenschaft des span-Elements von none in inline ändern. Das Ergebnis? Es gibt jetzt eine Race-Bedingung.

Was passiert, wenn der Browser das CSSOM nicht vollständig heruntergeladen und erstellt hat, wenn wir das Skript ausführen möchten? Die Antwort ist einfach und nicht sehr leistungsstark: Der Browser verzögert die Skriptausführung und die DOM-Erstellung, bis der Download und die Erstellung des CSSOM abgeschlossen sind.

Kurz gesagt führt JavaScript viele neue Abhängigkeiten zwischen dem DOM, dem CSSOM und der JavaScript-Ausführung ein. Dies kann dazu führen, dass der Browser erhebliche Verzögerungen beim Verarbeiten und Rendern der Seite auf dem Bildschirm verursacht:

  • Es ist wichtig, dass sich das Skript im Dokument befindet.
  • Wenn im Browser ein Skript-Tag erkannt wird, wird die DOM-Konstruktion angehalten, bis das Skript beendet ist.
  • JavaScript kann das DOM und das CSSOM abfragen und ändern.
  • Die JavaScript-Ausführung wird pausiert, bis das CSSOM bereit ist.

Im Wesentlichen bezieht sich „Optimierung des kritischen Rendering-Pfads“ auf das Verständnis und die Optimierung des Abhängigkeitsdiagramms zwischen HTML, CSS und JavaScript.

Parserblockierung und asynchrones JavaScript

Die JavaScript-Ausführung ist standardmäßig "Parser-Blockierung": Wenn der Browser im Dokument auf ein Skript stößt, muss er die DOM-Erstellung anhalten, die Kontrolle an die JavaScript-Laufzeit übergeben und das Skript ausführen lassen, bevor mit der DOM-Erstellung fortgefahren wird. Das haben wir in unserem Beispiel mit einem Inline-Skript in Aktion gesehen. Tatsächlich blockieren Inline-Skripts immer den Parser, es sei denn, Sie schreiben zusätzlichen Code, um die Ausführung zu verzögern.

Was ist mit Skripts, die über ein Skript-Tag eingefügt werden? Nehmen wir unser vorheriges Beispiel und extrahieren wir den Code 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, ist zu erwarten, dass beide sich gleich verhalten. In beiden Fällen pausiert der Browser das Skript 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 Skript von der Festplatte, aus dem Cache oder von einem Remote-Server abgerufen wird. Dadurch kann sich der kritische Rendering-Pfad um zehn bis Tausende Millisekunden verzögern.

Standardmäßig blockiert JavaScript immer die Parser. Da der Browser nicht weiß, welche Aktion das Skript auf der Seite plant, wird vom Worst-Case-Szenario ausgegangen, sodass der Parser blockiert wird. Durch ein Signal für den Browser, dass das Skript nicht genau an dem Punkt ausgeführt werden muss, an dem es referenziert wird, kann der Browser mit der DOM-Erstellung fortfahren und das Skript ausführen lassen, wenn es bereit ist, z. B. nachdem die Datei aus dem Cache oder einem Remote-Server abgerufen wurde.

Dazu markieren wir unser Skript als async:

<!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 Skript-Tag das Keyword "async" hinzufügen, wird der Browser angewiesen, die DOM-Erstellung nicht zu blockieren, während das Skript verfügbar ist. Dies kann die Leistung erheblich verbessern.

Feedback