Arno Welzel wrote:
> Thomas 'PointedEars' Lahn schrieb:
>> Arno Welzel wrote:
>>> […] Performanceprobleme erkenne in der *Praxis* ich keine,
>>> selbst wenn die Performanceunterschiede verschiedener Umsetzungen
>>> theoretisch messbar sein mögen.
>>
>> Je länger man Deine Website anschaut, umso mehr Arbeitsspeicher frisst
>> sie, weil immer mehr davon für immer neue Objekte reserviert werden muss
>> –
>> unnötiger Weise. Irgendwann wird dann der Tab oder der Browser vom OOM-
>> Killer abgeschossen oder das System hängt. Das ist nicht nur theoretisch
>> messbar (was BTW ein Widerspruch in sich ist; Messungen sind immer
>> Praxis).
>
> Aha - und welcher Umgebung soll das sein?
Chromium “53.0.2785.143 Built on 8.6, running on Debian stretch/sid (64-
bit).” Nach 10 Minuten auf der Website ist der Speicherbedarf des Tabs
(siehe Chromium Task-Manager, Shift+Esc) um ca. 7.14 MiB angestiegen. Man
kann beobachten, wie der Speicherbedarf im 5-Sekunden-Takt von kontinierlich
ansteigt. Das korreliert genau mit dem wiederholten überflüssigen Erzeugen
der XHR-Instanz, wie man in der Spalte “Network” und mit “Profiles”, “Record
Allocation Timeline” sieht (zuerst steigt der Speicherbedarf, dann gibt es
die Netzwerkaktivität).
Start: 21:12 CET
Spalte “Memory”: 60'552 KiB
Ende: 21:22 CET
Spalte “Memory”: 67'312 KiB
Das Ergebnis ist zuverlässig (nach “uncached reload”) reproduzierbar.
Und dabei habe ich *nichts* auf der Website oder im Browser gemacht ausser
gelegentlich zwischen Browser-Task-Manager und Newsreader (nicht im Browser)
hin- und herzuschalten.
Das Problem gibt es nicht nur auf Deiner Website (sondern besonders
problematisch auch bei Facebook und Twitter, die auch über XHR den Status
aktualisieren), aber die Ursache ist dieselbe. Denn wenn man nichts macht
oder XHR richtig macht, passiert es nicht.
> Hier mit Linux Mint 14 und Firefox 49 lässt sich das nicht reproduzieren.
> Auch nach mehrere Stunden ist der Speicherbedarf nicht angestiegen,
Wie misst Du das?
> der OOM-Killer wird logischerweise auch nicht aktiv und ein
> hängendendes System habe ich ebenfalls nicht.
Du solltest auch nicht davon ausgehen, dass Deine Website die einzige ist,
die im Browser angezeigt wird.
> Eine kurzer Vergleich
Was heisst „kurz“?
> mit Windows 7 und Firefox 49 sowie IE 11 wie auch Firefox unter MacOS hat
> ebenfalls keine Auffälligkeiten gezeigt.
Hast Du nur in Firefox 49 und IE 11 getestet?
>> Über den Rest Deiner DAU-dummen Reaktion breiten wir lieber den Mantel
>> des Schweigens.
>
> Zeige mir, was an der verwendeten switch()-Anweisung konkret *falsch*
> ist oder lass' es.
Ich schrieb „_unnötig bis_ falsch“. Es hilft Deiner Argumentation nicht,
wenn Du mich verkürzt wiedergibst.
Weshalb ich das schrieb, dachte ich, sei offensichtlich.
Zur besseren Lesbarkeit habe die Einrückung reduziert und den Quelltext
lesbarer formatiert (auch etwas, an dem Du noch arbeiten kannst):
| req.onreadystatechange = function () {
| switch (req.readyState)
| {
| case 4:
| […]
| break;
|
| default:
| return false;
| break;
| }
| };
Die switch-Anweisung befindet sich *alleinstehend* in einer *Funktion*.
Somit ist die return-Anweisung überflüssig, denn nach der switch-Anweisung
wird die Funktion ohnehin verlassen:
req.onreadystatechange = function () {
switch (req.readyState)
{
case 4:
[…]
break;
default:
break;
}
};
Schreibt man die return-Anweisung hin, ist die zweite break-Anweisung
überflüssig, denn die Funktion wird ja schon durch die return-Anweisung
verlassen:
req.onreadystatechange = function () {
switch (req.readyState)
{
case 4:
[…]
break;
default:
return false;
}
};
Es handelt sich ausserdem um einen Event-Listener, der an eine Event-
Handler-Eigenschaft zugewiesen wurde. Da ist es wesentlich, wie der
Rückgabewert aussieht, denn ein Rückgabewert kann zur Vermeidung der
Standardaktion führen (das ist nicht immer “false”, kann auch “true” sein).
Man sollte also nicht einfach blind einen Wert zurückgeben, weil „das schon
funktioniert“, sondern sich diesen gut überlegen, und im Zweifelsfall "gar
nichts" (d. h. “undefined”) zurückgeben:
req.onreadystatechange = function () {
switch (req.readyState)
{
case 4:
[…]
break;
default:
return;
}
};
Das ist aber schon das Standardverhalten ohne return-Anweisung:
req.onreadystatechange = function () {
switch (req.readyState)
{
case 4:
[…]
break;
default:
}
};
Will man die Funktion erweitern, also Anweisungen nach der switch-Anweisung
hinzufügen, muss man sich daran erinnern, dass da noch eine return-Anweisung
in der switch-Anweisung steht; solange wundert man sich, weshalb der neue
Code manchmal nicht ausgeführt wird:
req.onreadystatechange = function () {
switch (req.readyState)
{
case 4:
[…]
break;
default:
return false;
break;
}
/*
* _Halb_toter Code; kann je nach readyState Probleme verursachen;
* schlecht testbar.
*/
};
In der default-Klausel steht kein (sinnvoller) Code, sie kann also
genausogut weggelassen werden.
req.onreadystatechange = function () {
switch (req.readyState)
{
case 4:
[…]
break;
}
};
Lässt man die default-Klausel nicht weg, so lässt sich dies weniger
fehlerträchtig schreiben:
req.onreadystatechange = function () {
if (req.readyState == 4)
{
// …
}
else
{
return;
}
/*
* _Halb_toter Code; kann je nach readyState Probleme verursachen;
* schlecht testbar.
*/
};
Spätestens jetzt sieht man, dass man den Code stark vereinfachen kann,
wodurch er weniger fehlerträchtig und leichter wartbar wird:
req.onreadystatechange = function () {
if (req.readyState == 4)
{
// …
}
/*
* Evtl. Code hier nun nicht mehr tot; gut testbar.
*/
};
oder (“early return”, “gauntlet“):
req.onreadystatechange = function () {
if (req.readyState != 4) return;
// …
/*
* Evtl. Code hier nun nicht mehr tot; gut testbar.
*/
};
> Und zum Thema "Sicherheit": zeige mir, welches ausnutzbare(!")
> Sicherheitsproblem bei meiner Verwendung von windows.setTimeout()
> übergibt.
Erstens heisst es window.setTimeout().
Zweitens kann der Code, wenn er als String übergeben wird, nur auf global
verfügbare Symbole zugreifen. Das ist für sich schon ein Problem, auch ein
Sicherheitsproblem. Denn jedes fremde Script, was im Dokument geladen wird,
kann die Werte im globalen Ausführungskontext manipulieren, also zum
Beispiel die Funktion überschreiben. Sogar jede Extension und jedes Plugin
kann das, weil der globale Kontext an das Tab/Fenster gekoppelt ist.
Drittens ergibt sich das ausnutzbare Sicherheitsproblem dadurch, dass Du als
Autor nicht Sicherheitsfeatures des Browsers wie Content Security Policy
nutzen kannst, solange Du den Code als String übergibst (siehe Referenz).
Kein heutzutage sinnvoll scriptbarer Browser erfordert noch die String-
Variante. Wie aus der ECMAScript Support Matrix hervorgeht (ausnahmsweise,
weil dieses Feature mit dem Netscape DOM eingeführt wurde, bevor es die
Trennung zwischen Sprache und DOM gab), wird die Variante mit
Funktionsausdruck/-referenz spätestens seit Mozilla 1.0 und Internet
Explorer 5.5 unterstützt. (Das würdest Du unter anderem wissen, wenn Du
unter anderem die ES Matrix auch mal *lesen* würdest.)
Durch die String-Variante ergeben sich also gegenüber der Variante mit
Funktionsausdruck/-referenz *keinerlei* Vorteile und *nur* Nachteile.
> Und ja, die Problematik von eval() ist mir sehr wohl bewusst
> und ja, wenn ich es mal umbaue, werde ich sicher eine andere Lösung
> bevorzugen.
Die Lösung ist trivial; ich hatte sie Dir sogar bereits hingeschrieben.
Somit kann ich nur hoffen, dass ich mit diesem Posting nicht *wieder* meine
kostbare Freizeit (an Dich) verschwendet habe.