Geschwindigkeit optimieren
nemoinho
- javascript
hallo an alle Gewillten,
ich habe mal eine kleine Frage bezüglich der Optimierung meines codes.
Folgende Ausgangsstellung ich habe einen knoten mit mehreren kindknoten und möchte jetzt das alle diese kindknoten in einen wrapper div kommen, welcher nicht durch css verändert werden darf, soll heißen der wrapper darf an der Darstellung nichts ändern.
Ich skizziere mal das HTML und danach meine bisherige JS-Lösung.
<!-- Ausgangsstellung -->
<div id="container">
<h1>Überschrift</h1>
<p>Text</p>
<!-- usw. -->
</div>
<!-- Ziel -->
<div id="container">
<!-- die stylesheet müssen!! in das Element, weil ich nicht sicher sein kann, das nicht eine falsche css-regel diese angaben überschreibt! -->
<div style="margin:0;padding:0;border:0;outline:0;background:transparent;width:auto;height:auto;">
<h1>Überschrift</h1>
<p>Text</p>
<!-- usw. -->
</div>
</div>
//helfer-funktion, ist buggy, aber reicht hierfür aus!
function create(name,attr,childs){
var i,j;
name = document.createElement(name);
if(attr && typeof(attr) == 'object')
for(i in attr)
if(typeof(attr[i]) == 'object')
for(j in attr[i])
name[i][j] = attr[i][j];
else
name[i] = attr[i];
if(childs && childs.length)
for(i = childs.length; i--;)
name.insertBefore(childs[i], name.firstChild);
return name;
}
// Ab hier will ich die geschwindigkeit verbessern!
var container = document.getElementById('container');
var wrapper = create('div',{'style':{
'margin':'0',
'padding':'0',
'border':'none',
'background':'transparent',
'outline':'none',
'width':'auto',
'height':'auto'
}});
var tmp, i;
for(tmp = container.childsNodes, i = tmp.length; i--;)
wrapper.insertBefore(tmp[i], wrapper.firstChild);
so meine Frage ist nun genauer definiert, wie groß ist der Unterschied zwischen appendChild() und insertBefore() bezüglich der Geschwindigkeit und gibt es noch großen Optimierungsbedarf, ab der variable 'container' abwärts?
Danke schonmal im vorraus,
weil dieses Konstrukt versaut mir meine Performance... und ja mit einer Klasse oder ID wäre das schneller, sogar ca. 1,5 bis 2 mal so schnell, aber das will ich auf jeden Fall nicht!
mfG Felix Nehrke
P.S.: Besuche unseren Blog: http://www.pommes-blog.de
Dich dürfte dieser Viedeo Vortrag zu Javascript und Performance interessieren
http://www.youtube.com/watch?v=mHtdZgou0qU
mfg Beat
Dich dürfte dieser Viedeo Vortrag zu Javascript und Performance interessieren
jo, tut er auch, aber erstens kenne ich ihn schon und zweitens setze ich bestimmt 90% davon so oder so schon um, also daran liegt es nicht wirklich, es ist halt wirklich nur die Frage, wie ich am elegantesten die vielen kind-knoten in den wrapper verschoben krieg...
aber trotzdem danke
mfG Felix Nehrke
P.S.: Besuche unseren Blog: http://www.pommes-blog.de
Hi,
Folgende Ausgangsstellung ich habe einen knoten mit mehreren kindknoten und möchte jetzt das alle diese kindknoten in einen wrapper div kommen, welcher nicht durch css verändert werden darf, soll heißen der wrapper darf an der Darstellung nichts ändern.
So ganz ist mir die Notwendigkeit noch nicht klar.
Warum muss #container durch einen Wrapper ersetzt werden, warum kannst du nicht #container wie gewünscht formatieren?
Soll das Script in fremden Seiten arbeiten, wo du die Formatierung von #container nicht unter Kontrolle hast? Da könnten aber auch für dein Wrapper-Div Formatierungen gelten, die du nicht unter Kontrolle hast (schon allein über den Element-Selektor div). Da würde ich mich fragen, ob es nicht generell performanter wäre, wenn du alle CSS-Eigenschaften, auf die du dabei Wert legst, für #container selber explizit per JavaScript setzt ...?
so meine Frage ist nun genauer definiert, wie groß ist der Unterschied zwischen appendChild() und insertBefore() bezüglich der Geschwindigkeit
Das dürfte generell erst Mal kaum einen Unterschied machen. Beide hängen einen Knoten "irgendwo" in einen DOM-(Teil-)Baum ein, lediglich die Position ist eine andere.
Bei deiner derzeit gewählten Form,
wrapper.insertBefore(knoten, wrapper.firstChild)
muss allerdings auch wrapper.firstChild jedes Mal neu ermittelt werden - da könnte appendChild ein Quentchen schneller sein, weil du die Position da nicht im Script selber angeben musst, sondern den Interpreter sich selber drum kümmern lässt.
und gibt es noch großen Optimierungsbedarf, ab der variable 'container' abwärts?
Hängt das wrapper-Element zu dem Zeitpunkt, wo du die Kindknoten einhängst, schon im Dokument? (Sieht nach deinem Beispielcode nicht so aus, aber ich frag trotzdem mal ...)
Es erst nach dem Einhängen aller Kindelemente ins Dokument einzufügen, sollte auf jeden Fall die schnellere Alternative sein.
Generell gilt: DOM-Manipulationen gehen idR. immer schneller, wenn sie "ausserhalb" des DOMs des aktuellen Dokumentes stattfinden.
#container auch erst aus dem Dokument herauszulösen (removeChild), bevor dessen Kindknoten "entnommen" werden, könnte ggf. noch etwas bringen. (Sofern vom Ablauf her möglich; kann ja auch danach wieder engefügt werden, wenn nötig.)
for(tmp = container.childsNodes, i = tmp.length; i--;)
wrapper.insertBefore(tmp[i], wrapper.firstChild);
Auch wenn das "Verschieben" von DOM-Knoten die Hauptbremse sein dürfte, kann man hier evtl. noch ein bisschen optimieren.
Dass in der Abbruchbedingung die Länge der childNodes immer wieder neu ermittelt werden muss, bremst auch ein bisschen. (Und wrapper.firstChild jedes Mal auf's neue bestimmen beim Einfügen eines Knotens auch, s.o.)
Ich würde es ohne eigenen Zähler mit einer While-Schleife probieren, und appendChild statt insertBefore nutzen:
var fChild;
while(fChild = container.firstChild) {
wrapper.appendChild(fChild);
}
So lange container noch ein firstChild hat, hänge dieses hinten an wrapper dran.
Die Hilfsvariable fChild deshalb, damit container.firstChild nicht beim Anhängen an wrapper noch ein zweites Mal ausgewertet werden muss - der Einsatz des .-Operators "kostet" relativ viel. Gerade im Bereich solcher Mirco-Optimierung kann sich auch das bemerkbar machen.
Ach ja, P.S.: So, wie du das ganze hier aufbereitet hast, gehe ich davon aus, dass ein "Umhängen" der Kindknoten per
wrapper.innerHTML = container.innerHTML;
container.innerHTML = "";
keine für dich ernsthaft in Betracht zu ziehende Alternative wäre?
Das könnte noch mal deutlich schneller sein, als "richtige" DOM-Manipulation; allerdings gehen dabei z.B. Eventhandler verloren - schliesslich werden damit die Elemente nicht wirklich umgehängt, sondern schlicht und einfach neu erzeugt (und die "alten" anschliessend weggeworfen).
MfG ChrisB
Moin Moin,
So ganz ist mir die Notwendigkeit noch nicht klar.
Warum muss #container durch einen Wrapper ersetzt werden, warum kannst du nicht #container wie gewünscht formatieren?
Weil ich via JS die Höhe des gesammten Inhalt ermitteln will, und es ist bedeutend schneller nur von einem element die offsetHeight abzufragen, als von jedem einzelnen, zumal auch noch margin's mit in die höhe einfliessen sollen.
Soll das Script in fremden Seiten arbeiten, wo du die Formatierung von #container nicht unter Kontrolle hast? Da könnten aber auch für dein Wrapper-Div Formatierungen gelten, die du nicht unter Kontrolle hast (schon allein über den Element-Selektor div). Da würde ich mich fragen, ob es nicht generell performanter wäre, wenn du alle CSS-Eigenschaften, auf die du dabei Wert legst, für #container selber explizit per JavaScript setzt ...?
1. Ja es soll auch auf Seiten laufen, auf die ich nicht direkt einfluss habe und
2. Das Design/Layout des Containers sowie des Inhalt sollen unabhängig vom Script sein, daher diese "Zwischenschicht".
so meine Frage ist nun genauer definiert, wie groß ist der Unterschied zwischen appendChild() und insertBefore() bezüglich der Geschwindigkeit
Das dürfte generell erst Mal kaum einen Unterschied machen. Beide hängen einen Knoten "irgendwo" in einen DOM-(Teil-)Baum ein, lediglich die Position ist eine andere.
Bei deiner derzeit gewählten Form,
wrapper.insertBefore(knoten, wrapper.firstChild)
muss allerdings auch wrapper.firstChild jedes Mal neu ermittelt werden - da könnte appendChild ein Quentchen schneller sein, weil du die Position da nicht im Script selber angeben musst, sondern den Interpreter sich selber drum kümmern lässt.
Das dachte ich mir bereits und stimmt die zusätzliche abfrage ist langsamer, allerdings ist diese Methode immernoch schneller als das Array umzudrehen und dann alles hinten anzufügen :)
und gibt es noch großen Optimierungsbedarf, ab der variable 'container' abwärts?
Hängt das wrapper-Element zu dem Zeitpunkt, wo du die Kindknoten einhängst, schon im Dokument? (Sieht nach deinem Beispielcode nicht so aus, aber ich frag trotzdem mal ...)
Es erst nach dem Einhängen aller Kindelemente ins Dokument einzufügen, sollte auf jeden Fall die schnellere Alternative sein.Generell gilt: DOM-Manipulationen gehen idR. immer schneller, wenn sie "ausserhalb" des DOMs des aktuellen Dokumentes stattfinden.
#container auch erst aus dem Dokument herauszulösen (removeChild), bevor dessen Kindknoten "entnommen" werden, könnte ggf. noch etwas bringen. (Sofern vom Ablauf her möglich; kann ja auch danach wieder engefügt werden, wenn nötig.)
Ja daran hatte ich noch gar nicht gedacht, aber ich probier da mal ein bisschen rum, wahrscheinlich bringt das dann auch noch mal nen ganzes Stück
for(tmp = container.childsNodes, i = tmp.length; i--;)
wrapper.insertBefore(tmp[i], wrapper.firstChild);
> Auch wenn das "Verschieben" von DOM-Knoten die Hauptbremse sein dürfte, kann man hier evtl. noch ein bisschen optimieren.
>
> Dass in der Abbruchbedingung die Länge der childNodes immer wieder neu ermittelt werden muss, bremst auch ein bisschen. (Und wrapper.firstChild jedes Mal auf's neue bestimmen beim Einfügen eines Knotens auch, s.o.)
>
> Ich würde es ohne eigenen Zähler mit einer While-Schleife probieren, und appendChild statt insertBefore nutzen:
> ~~~javascript
var fChild;
> while(fChild = container.firstChild) {
> wrapper.appendChild(fChild);
> }
So lange container noch ein firstChild hat, hänge dieses hinten an wrapper dran.
Die Hilfsvariable fChild deshalb, damit container.firstChild nicht beim Anhängen an wrapper noch ein zweites Mal ausgewertet werden muss - der Einsatz des .-Operators "kostet" relativ viel. Gerade im Bereich solcher Mirco-Optimierung kann sich auch das bemerkbar machen.
Von einer while-schleife sehe ich i.d.R. ab, da sie oft langsamer ist als eine for-schleife, allerdings ist dies ja bei JS nicht unbedingt der Fall, zumindest kein relevanter, aber das gewählte Konstrukt ist schon beabsichtigt, den dadurch wird nur einmal ermittelt, wie viele Kindknoten da sind und dann nur kontrolliert, ob die Schleifenbedingung noch wahr ist, weil in js alles wahr ist, was nicht false, 0, null oder undefined ist.
Wobei ich jetzt in der Vorschau grade erst sehe, dass bei deiner Variante ja eine komplette Abfrage wegfällt, sprich dürfte schneller sein, ich prob das mal aus, wenn ich das Script wieder offen habe, schreibe nämlich ganz aktuell an nem kleinen Hack, damit ich nicht jeden Tag alles möglich nachlesen muss, einstellen muss, weil das etwas umständlich wird bei der Bundeswehr...
Ach ja, P.S.: So, wie du das ganze hier aufbereitet hast, gehe ich davon aus, dass ein "Umhängen" der Kindknoten per
wrapper.innerHTML = container.innerHTML;
container.innerHTML = "";
> keine für dich ernsthaft in Betracht zu ziehende Alternative wäre?
> Das könnte noch mal deutlich schneller sein, als "richtige" DOM-Manipulation;
Nein, dass ist auch keine Möglichkeit, vor allem aufgrund besagter Problematik mit den Eventhandlern.
Aber mir ist auch noch meine äußerst dämliche Kurzsichtigkeit aufgefallen, denn die create-funktion ist ja "rough and ready" und dabei mach ich eine ziemlich dumme Abfrage:
~~~javascript
...
if(typeof(attr[i]) == 'object')
...
denn es ist ja eigentlich nur ein object möglich, das style-object, also sollte es aus Performancegründen eher so da stehen, weil die Geschwindigkeit der create-funktion ja in das script mit einfließt:
...
if(i == 'style')
...
Außerdem ist mir noch das cssText Attribute eingefallen, welches eigentlich in allen wichtigen Browsern, inkl. IE, funktioniert, damit könnte ich die Funktion nochmal deutlich beschleunigen, da nun nur noch einmal pro wrapper auf das DOM zugegriffen werden müsste
var wrapper = create('div',{'style':{
'cssText':'margin:0;padding:0;border:none;background:transparent;outline:none;width:auto;height:auto;'
}});
mfG Felix Nehrke
P.S.: Besuche unseren Blog: http://www.pommes-blog.de
Hi,
So ganz ist mir die Notwendigkeit noch nicht klar.
Das hat sich inzwischen nur in sofern geändert, dass mir jetzt noch weniger klar ist, was du wirklich erreichen willst ...
Warum muss #container durch einen Wrapper ersetzt werden, warum kannst du nicht #container wie gewünscht formatieren?
Weil ich via JS die Höhe des gesammten Inhalt ermitteln will, und es ist bedeutend schneller nur von einem element die offsetHeight abzufragen, als von jedem einzelnen, zumal auch noch margin's mit in die höhe einfliessen sollen.
Und warum reicht dir dann die offsetHeight von #container nicht, warum müssen die Elemente erst in #wrapper umgehängt werden?
Schon möglich, dass da noch weitere CSS-Eigenschaften im Spiel sind, die das unmöglich machen - aber ohne, dass du konkreter wirst, kann ich dir da auch nicht mehr viel Tipps geben oder Vorschläge machen.
Ein Online-Beispiel nebst Erklärung, was *wirklich* ermittelt werden soll und zu welchem Zwecke, könnte dem Verständnis vermutlich noch am ehesten weiterhelfen.
Das dachte ich mir bereits und stimmt die zusätzliche abfrage ist langsamer, allerdings ist diese Methode immernoch schneller als das Array umzudrehen und dann alles hinten anzufügen :)
Welches Array denn "umdrehen"?
Generell gilt: DOM-Manipulationen gehen idR. immer schneller, wenn sie "ausserhalb" des DOMs des aktuellen Dokumentes stattfinden.
#container auch erst aus dem Dokument herauszulösen (removeChild), bevor dessen Kindknoten "entnommen" werden, könnte ggf. noch etwas bringen. (Sofern vom Ablauf her möglich; kann ja auch danach wieder engefügt werden, wenn nötig.)
Ja daran hatte ich noch gar nicht gedacht, aber ich probier da mal ein bisschen rum, wahrscheinlich bringt das dann auch noch mal nen ganzes Stück
Nein, das kannst du wieder vergessen, wenn du die Maße von Elementen ermitteln willst - wenn diese nicht im DOM hängen, haben sie auch keine Maße (IIRnottotallyinC)
Von einer while-schleife sehe ich i.d.R. ab, da sie oft langsamer ist als eine for-schleife, allerdings ist dies ja bei JS nicht unbedingt der Fall,
Warum sollte die generell langsamer sein?
Beide Schleifen haben jeweils eine Bedingung zu überprüfen und davon abhängig zu entscheiden, ob sie weiterlaufen, oder nicht.
(Im Bereich der erwähnten "Micro-Optimierung" gilt dann für for-Schleifen allerdings wieder, der pre-increment- bzw. pre-decrement-Variante den Vorzug vor der entsprechenden post-Variante zu geben. i-- muss erst eine "Hilfsvariable" anlegen, die den Inhalt der Variablen *vor* der Dekrementierung aufnimmt, weil dieser den Rückgabewert des Ausdrucks i-- darstellt - bei --i hingegen ist der um eins verringerte Wert von i direkt auch der Wert des Ausdrucks, so dass die Zwischenspeicherung des aktuellen Wertes von i entfällt.)
Aber mir ist auch noch meine äußerst dämliche Kurzsichtigkeit aufgefallen, denn die create-funktion ist ja "rough and ready" und dabei mach ich eine ziemlich dumme Abfrage:
...
if(typeof(attr[i]) == 'object')
...
Ja, aber die create-Funktion läuft nur ein einziges Mal, um das Wrapper-Element zu erstellen - die dürfte also deutlich zu vernachlässigen sein gegenüber allem, was in Schleifen abgearbeitet wird.
> Außerdem ist mir noch das cssText Attribute eingefallen, welches eigentlich in allen wichtigen Browsern, inkl. IE, funktioniert, damit könnte ich die Funktion nochmal deutlich beschleunigen
Auch hier - das wird vermutlich nur minimal was bringen.
Das, was in den Schleifen mit den ChildNodes von Elementen gemacht wird, das ist wenn die Baustelle, an der sich Optimierung wirklich lohnen könnte, sofern noch welche möglich.
MfG ChrisB
--
Light travels faster than sound - that's why most people appear bright until you hear them speak.