JavaScript Performance

Auch wenn der Blog sich vorrangig mit serverseitigen Optimierungen befasst, darf man ja die Client-Seite nicht außer Acht lassen. Deshalb heute ein kleiner Beitrag zum Thema JavaScript-Optimierung.

Inspiriert wurde ich durch einen Beitrag bei Dr. Web.
Es geht darum, inwiefern alte Konstrukte durch neuere – und vor allem performantere – Äquivalente ersetzt werden können.

Leider wurde keine der dort gemachten Empfehlungen durch irgendeinen Satz begründet, weshalb diese Varianten schneller sein sollen. Deshalb versuche ich das mal.

Punkt 1:

//Alt:
element.onclick = new Function("...");
//Neu:
element.onclick = function("...");

Ehrlich gesagt habe ich das nie anders gemacht und auch in Fremdscripten nicht gesehen. Bei der oberen Variante wird eben ein Objekt angelegt, unten nur eine Funktion. Das Objekt ist speicherintensiver, deshalb wird die untere Variante empfohlen. Kann ich mich anschließen – aber wie gesagt: Das als ersten Punkt zu wählen, ist nicht so doll gelungen auf Grund der mangelnden Verbreitung.

2. Punkt:

//Alt:
return eval("document.forms[0]." + field);
//Neu:
return document.forms[0][field];

Hier wird es schon sehr viel interessanter. Jeder ernsthafte Programmierer scheut eval – und das zu recht, weil dadurch immer wieder Sicherheitslücken entstehen. Überhaupt ist eval programmiertechnisch nicht sauber. Ich versuche es in 99% der Fälle zu vermeiden (auch wenn es manchmal etwas mehr Code bedeutet).
Aus Performance-Sicht macht obige Aussage ebenfalls Sinn, denn eval() ist eine Funktion, die den ihr übergebenen String zuerst parsen muss (gerade das ist ja die Schwachstelle, weil eval() alles Mögliche ausführen kann). Wieso aber sollte man eine Funktion aufrufen, wenn der gesuchte Wert bereits in einem vorliegenden Array zu finden ist? Genau, man sollte es nicht 😉
Auch hier gilt mal wieder: eval ist evil.

Punkt 3:

//Alt:
with(document.forms[0]) {
alert(elements.length);
}
//Neu:
var form = document.forms[0];
alert(form.elements.length);

Die with-Schreibweise ist eine schön kurze Version, wenn man auf Attribute oder methoden eines bestimmten Objektes nacheinander zugreifen möchte. Nachteil dabei ist, dass dieses Objekt temporär im Speicher gehalten werden muss.
Der Tipp ist deshalb nur sinnvoll, wenn man nur eine einzige Anweisung innerhalb des with-Blocks ausführt. Aber dafür ist der Block ja eigentlich auch nicht geschaffen. Hat man mehrere Anweisungen auf ein Objekt auszuführen, denke ich, dass das with performanter ist, da dann das Objekt nicht jedes Mal neu in den Speicher geladen werden muss.
Beispiel:

with(document.getElementById('id')) {
style.color = "red";
innerHTML = "Hallo";
}

4. Punkt (auf der Tagesordnung):

//Alt:
try {
...
} catch() {
...
}
 
//Neu:
if() {
...
} else {
...
}

Dieses Beispiel verstehe ich nicht ganz, denn if-else und try-catch kommen eigentlich aus ganz verschiedenen Ecken (und haben deshalb auch unterschiedliche Aufgaben). Mit try-catch fängt man Fehler ab. Gut, man kann vorbeugend if-else benutzen (muss dann aber an wirklich alle möglichen Fehlerquellen denken). Also für mich 2 Paar Schuhe…

Punkt 5 (Variablen lokal setzen):

//Alt:
var a, b = 1;
function test() {
var c = a + b;
}
 
//Neu:
function test() {
var a, b = 1;
var c = a + b;
}

Gut, das ist aus zwei Gründen sinnvoll: Zum einen wird die Variable gekapselt. Dadurch kommt es nicht zu unerwünschten Überschreibungen. Außerdem muss für die Variable nur dann Speicher reserviert werden, wenn die entsprechende Funktion auch aufgerufen wird.
Prädikat: Besonders sinnvoll!

Punkt 6 (for-in-Schleifen):

//Alt:
for (var i in array) {
alert(array[i]);
}
 
//Neu:
var length = array.length;
for (var i = 0; i < length; i ++) {
alert(array[i]);
}

Auch hier stimme ich zu. for-in-Schleifen sind nur sinnvoll bei Arrays, bei denen der Index nicht fortlaufend ist (also wenn man die Positionen der einzelnen Elemente selbst festgelegt hat).
Schön: Auch der Umstand, dass man vor der Schleifenbedingung schon die Arraylänge überprüfen sollte, damit diese nicht in jedem Durchlauf ermittelt werden muss.

Und noch einer:

//Alt:
setInterval("func()", 10000)
setTimeout("func(" + value + ")", 10000);
 
//Neu:
setInterval(func, 10000)
setTimeout (function() {func(value)}, 10000);

Dies ist mir neu gewesen, aber ich nutze auch recht selten solche Funktionen. Für AJAX sind sie manchmal wichtig, wenn man eine periodische Aktualisierung der Daten erreichen möchte, aber sonst eigentlich nur bei Spielereien wie Animationen oder Spielen.
Macht aber nichtsdestotrotz Sinn, da der String ja erst interpretiert werden muss vor dem Ausführen, während die Funktion direkt ausgeführt werden kann. Kann ich also auch empfehlen.

Was ich jetzt nicht geprüft habe, ist, ob diese Dinge auch in sämtlichen gängigen Browsern funktionieren. Ich denke aber mal schon…

Der letzte Tip ist natürlich immer gut: Alles, was vom Server geladen werden muss, sollte komprimiert werden. Spezielle JS-Komprimier-Tools können die Dateigröße siginifikant senken.

Ist es doch ein ganz schön langer Beitrag geworden. So war das nicht geplant, aber ich hoffe euch freuts 😉

Jan hat 152 Beiträge geschrieben

14 Kommentare zu “JavaScript Performance

  1. Ich denke, dass die Tipps von Dr. Web im Grunde nicht wirklich bewegend sind, viele Sachen sind verdreht oder einfach nur falsch dargestellt – Ich denke nicht, dass der Autor ein wahrer JS Profi ist. Daher denke ich, dass man hier das auch nicht so stehen lassen sollte. Hier meine Anmerkungen:

    1.
    Der unterschied zwischen der direkten Funktionszuweisung und des Erstellen eines neuen Objekts ist richtig dargestellt. Allerdings kann man nicht in allen Fällen auf die hier suggerierte Möglichkeit zurückgreifen. Wenn man in der Funktion z.b. Variablen ansprechen möchten, welche sich im weitere Programmablauf noch ändern können, man aber auf den Wert der Zuweisung zurückgreifen möchte, klappt dies nur, indem man ein neues Objekt erstellt. Die direkte Funktionszuweisung, welche vor der direkten Ausführung nicht weiter vom Parser behandelt wurde, würde auf die dann aktuellen Werte zugreifen.
    Einschlägige Performance oder Speicher-Unterschiede sehe ich hier aber nicht.

    2.
    Die hier gezeigte von Verwendung der Funktion eval() ist wirklich sinnlos und ohne Begründung (wer kommt auf eine solche Idee?). Allerdings kann die Funktion in diversen Fällen auch Sinn machen. Ein direktes Sicherheitsproblem sehe ich hier auf JS Basis allerdings nicht, da ich mit einfachen Tools (z.b. Firebug) in meinem Browser sowieso alles ausführen lassen kann, was ich möchte. Sicherheitsrisiken sollten hier nur im Serverbereich eine Rolle spielen.

    3.
    Ich denke hier auch, dass with() performanter ist, wenn man mehrere Objekte darüber anspricht. Über diese Funktion bleibt der Verweis temporär im Speicher, mit der gezeigten „Verbesserung“ allerdings fest, benötigt somit in der Summa Summarum mehr Speicher als unsere erste Variante.

    4.
    Ein Vergleich von try-catch- ist zu einer if-else-Abfrage nicht möglich. Wenn man z.B. das bestehen bestimmter Objekte überprüfen möchte, würde die If-Schleife immer Fehler verursachen, da das Objekt nicht vorhanden ist und trotzdem ausgeführt werden soll. Das sind zwei Paar Schuhe, ganz richtig!

    5.
    Das Anlegen solcher Variablen und Objekte ist sowieso nicht optimal. Der ideale Aufbau liegt in einem festen Namespace, in dem die einzelnen Objekte mit ihren untergeordneten Variablen, Funktionen und weiterführenden Objekten liegen. Nur so hat man ein ordentliches DOM und alles übersichtlich und auch speicheroptimiert. Dass man dann natürlich lokale Variablen nicht global anlegen sollte, setze sich voraus.

    6.
    Stimme ich zu, allerdings sehe ich hier keine Performance Einbußen, im Gegenteil, für die zweite Variante müssen zusätzlich Variablen im Speicher abgelegt werden.

    7.
    Die erste Version ist hier notwendig, wenn man Variablen mit übergeben möchte. Alternativ kann man auch auf die Erstellung eines neuen Objekts mit new zurückgreifen. Man sollte im Allgemeinen beachten, dass die Quantität der gleichzeitig laufenden Interval und Timeout Funktionen in den Browser beschränkt ist. Wenn man trotzdem sehr viele benötigen sollte, muss man auf Queues zurückgreifen, welche dann nur ein Interval benutzen, welches die in Arrays gespeicherten Funktionen ausführt. Im jeden Fall ist das auch performanter.

    Ich hoffe, dass das jemand liest und dass ich nun die Punkte etwas berichtigt habe.

    Viele Grüße und Danke,
    Jürgen Vogel

  2. tk sagt:

    evtl. wäre ein Performancetest seitens PHP (wie der Blog hier auch heisst 😉 ) ganz interessant.

    bei meinen Tests war eine foreach-Schleife – vor allem bei größeren Arrays – schneller und weniger Speicherintensiv.

    Selbst diese Variante war „schneller“:
    $i = 0; while(isset($array[$i])) {   $bla = $array[$i];   ++$i; }

  3. Hm nette Beobachtungen, hiermit kann ich auch meinen JS Code wohl noch etwas optimieren. Aufgrund meiner vielen tausenden Zeilen Code wird das hin und wieder doch etwas „unschnell“ 🙂

  4. OliJun sagt:

    zum Thema with, versuch mal. ich denk das ist der grund, warum man with nicht nehmen soll

    performance

    var itera = 10000;

    startTime = new Date();
    with(document) {
    for(i=itera;i>0;i–) {
    getElementById(„ausg“).innerHTML = i;
    }
    }
    endtime = new Date();
    var diff1 = endtime-startTime;

    startTime = new Date();
    for(i=itera;i>0;i–) {
    document.getElementById(„ausg“).innerHTML = i;
    }
    endtime = new Date();
    var diff2 = endtime-startTime;

    document.getElementById(„ausg“).innerHTML = diff1+“ — „+diff2;

  5. ps3-fan sagt:

    Ich kann nur empfehlen sämtlichen Code oder Tipps (entsprechend visualisiert) immer auf sowas wie browsershots.org zu testen. Habe mit so mancher Optimierung in letzter Zeit schon böse Überraschungen erlebt.

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>