DOM Scripting mit weniger Kopfschmerzen
geschrieben am Montag, 3. Oktober, 2011
JavaScript. Die Sprache des Webs. So sehr diese Sprache auch im Web verwendet wird, wirkt sie auf viele Entwickler auf dem ersten und vielleicht auch zweiten Blick doch recht… schmutzig. Nicht ganz unschuldig daran ist die inkonsistente Implementierung der DOM-Methoden. Viele Entwickler greifen aus solch einem Grund auf JavaScript-Bibliotheken zurück, die die Implementierung der DOM-Methoden abstrahieren und somit ein angenehmes Arbeiten mit dem DOM ermöglichen (wie z.B. jQuery). Doch was wenn mal keine “Ich-bin-so-wunderbar”-JavaScript-Bibliothek wie jQuery gewünscht ist (nicht zuletzt aus dem Grund, weil sie gar nicht soooo Wunderbar ist)?
DOM-Zugriff
Der wahrscheinlich größte Knackpunkt am DOM Scripting ist wohl das DOM selbst. Das klingt im ersten Moment recht widersprüchlich und wenn Ich es nicht besser wüsste, ist es das auch. Der Grund dafür ist, dass das DOM getrennt von der JavaScript-Engine implementiert ist, was auch gar nicht so undurchdacht ist, da man ja nicht zwangsweise immer auf das DOM zugreifen möchte.
Allerdings bringt genau dies seine Nachteile mit sich. Jeder Zugriff auf das DOM dauert. Das wiederum bedeutet, dass man die Zugriffe auf das DOM möglichst gering halten sollte. Nur wie soll das funktionieren, wenn wir doch ständig das DOM manipulieren wollen? Es gibt da ein paar Kniffe, die unserem Problem entgegen wirken.
Wenn Ihr euch Ein bisschen was über Performance in JavaScript bereits durchgelesen habt, kennt Ihr ja schon den kleinen Trick mit dem Zwischenspeichern um eure Schleifen schneller zu machen. Nun wollen wir aber auf das DOM zugreifen, was uns doch eine Menge Steine in den Weg legt, was unsere Performance angeht.
for (i = 0; i < 100; i += 1) {
document.getElementById("foo").innerHTML += i + ", ";
}
Autsch. Wie Ihr seht, wird hier im obigen Code hundertmal iteriert. Und bei jedem Durchlauf wird auf das DOM zugegriffen. Eine sehr kostspielige Angelegenheit. Das muss nicht sein. Im folgenden Code reduzieren Wir die Zugriffe auf ein einziges Mal. Dies bedeteut zwar mehr Code, aber es lohnt sich. Je nach Browser ist der Code zehn- bis hundertfach schneller!
var i, content = "";
for (i = 0; i < 100; i += 1) {
content += i + ", ";
}
document.getElementById("foo").innerHTML += content;
Ähnliches Vorgehen können Wir auch bei Veränderungen von Attributen vornehmen. Auch hier gilt: Zwischenspeichern rettet eurer App das Leben!
// nicht gut
var padding = document.getElementById("foo").style.padding,
margin = document.getElementById("foo").style.margin;
// awesome
var style = document.getElementById("foo").style,
padding = style.padding,
margin = style.margin;
Ein weiterer guter und auch schneller Weg ist die Verwendung von Selektions-Apis. Neuere Browsers und ab IE 8 aufwärts bieten diese APIs um auf DOM-Elemente zuzugreifen. Die Selektor-Methoden sind immer schneller als eine selbst vorgenommene Selektion über andere DOM-Methoden. Gängige JavaScript-Bibliotheken verwenden diese APIs bereits, man sollte also immer auf dem aktuellsten Stand sein.
// ein bestimmtes Element
document.querySelector("#foo");
// Elemente mit einer bestimmten Klasse
document.querySelectorAll("#foo .selected");
Das DOM verändern
Selbstverständlich wollen Wir, nachdem Wir drauf zugegriffen und genug rumgerockt haben, das DOM unseren Wünschen entsprechend manipulieren. Aber auch hier haben Wir das Problem, dass die meisten Änderungen am DOM eine Menge Zeit kosten. Das Anpassen des DOM kann dazu führen, dass der Browser den Fensterinhalt neu ausgibt und häufig den "Fluss" der Elemente neu berechnet. Man spricht hier auch vom sogenannten Reflow. Paul Irish geht in einem seiner Screencasts näher drauf ein.
Nehmen Wir also an, Wir wollen einen Unterbaum erzeugen... Grundsätzlich könnte man es so machen:
var p, t;
p = document.createElement("p");
t = document.createTextNode("Text");
p.appendChild(t);
document.body.appendChild(p);
p = document.createElement("p");
t = document.createTextNode("Another one.");
p.appendChild(t);
document.body.appendChild(p);
Kann man bringen, ist aber alles andere schön. Es gibt einen besseren Weg. Wir können nämlich ein sogenanntes Dokumenten-Fragment erzeugen. Auf diesem rocken Wir rum, bis wir der Meinung sind, dass alle unsere Änderungen an das eigentliche Dokument gehängt werden können. Seht selbst.
var p, t, f;
f = document.createDocumentFragment();
p = document.createElement("p");
t = document.createTextNode("awesome");
p.appendChild(t);
f.appendChild(p);
p = document.createElement("p");
t = document.createTextNode("even more awesome");
p.appendChild(t);
f.appendChild(p);
document.body.appendChild(f);
Und schon haben Wir anstatt zwei nur noch eine Aktualisierung, was bedeutet, dass auch nur ein Reflow erzeugt wird.