In JavaScript sollten wir so oft wie möglich lokale Variablen anstelle globaler Variablen verwenden. Jeder kennt diesen Satz, aber wer hat ihn zuerst gesagt? Warum das tun? Gibt es dafür eine Grundlage? Wie groß ist der Leistungsverlust, wenn Sie dies nicht tun? In diesem Artikel werden die Antworten auf diese Fragen untersucht und grundsätzlich verstanden, welche Faktoren mit der Lese- und Schreibleistung von Variablen zusammenhängen.
【Original】JavaScript-Variablenleistung
【Autor】Nicholas C. Zakas
[Übersetzung] Warum sollten wir in JavaScript wann immer möglich lokale Variablen verwenden?
[Übersetzer] Mingda
Das Folgende ist eine Übersetzung des Originaltextes:
Bei der Frage, wie die JavaScript-Leistung verbessert werden kann, ist der am häufigsten gehörte Vorschlag die Verwendung lokaler Variablen anstelle globaler Variablen. Dies ist ein Rat, der mir in Erinnerung geblieben ist und den ich in meinen neun Jahren in der Webentwicklung nie in Frage gestellt habe. Er basiert auf der Handhabung der Scoping- und Identifier-Auflösungsmethode (Identifier-Auflösung) durch JavaScript.
Zunächst müssen wir klarstellen, dass Funktionen in JavaScript als Objekte verkörpert sind. Der Prozess der Erstellung einer Funktion ist eigentlich der Prozess der Erstellung eines Objekts. Jedes Funktionsobjekt verfügt über eine interne Eigenschaft namens [[Scope]], die die Bereichsinformationen zum Zeitpunkt der Erstellung der Funktion enthält. Tatsächlich entspricht das Attribut [[Scope]] einer Liste von Objekten (Variablenobjekten), und auf die Objekte in der Liste kann innerhalb der Funktion zugegriffen werden. Wenn wir beispielsweise eine globale Funktion A erstellen, enthält die interne Eigenschaft [[Scope]] von A nur ein globales Objekt (Global Object), und wenn wir in A eine neue Funktion B erstellen, enthält das Attribut [[Scope]] von B Zwei Objekte: Das Aktivierungsobjektobjekt der Funktion A befindet sich vorne und das globale Objekt (Global Object) hinten.
Wenn eine Funktion ausgeführt wird, wird automatisch ein ausführbares Objekt (Execution Object) erstellt und an eine Bereichskette (Scope Chain) gebunden. Die Bereichskette wird durch die folgenden zwei Schritte zur Identifikatorauflösung erstellt.
1. Kopieren Sie zunächst die Objekte in den internen Eigenschaften des Funktionsobjekts [[Scope]] der Reihe nach in die Bereichskette.
2. Zweitens wird beim Ausführen der Funktion ein neues Aktivierungsobjektobjekt erstellt, das dessen Definitionen, Parameter (Argumente) und lokale Variablen (einschließlich benannter Parameter) enthält . Die Vorderseite der Domänenkette.
Wenn während der Ausführung von JavaScript-Code ein Bezeichner gefunden wird, wird dieser in der Bereichskette des Ausführungskontexts (Ausführungskontext) basierend auf dem Namen des Bezeichners durchsucht. Beginnen Sie mit dem ersten Objekt in der Bereichskette (dem Aktivierungsobjekt der Funktion). Wenn es nicht gefunden wird, suchen Sie nach dem nächsten Objekt in der Bereichskette usw., bis die Definition des Bezeichners gefunden wird. Wenn das letzte Objekt im Bereich, das globale Objekt, nach der Suche nicht gefunden wird, wird ein Fehler ausgegeben, der den Benutzer darüber informiert, dass die Variable nicht definiert ist. Dies ist das im ECMA-262-Standard beschriebene Funktionsausführungsmodell und der Bezeichnerauflösungsprozess. Es stellt sich heraus, dass die meisten JavaScript-Engines tatsächlich auf diese Weise implementiert sind. Es ist zu beachten, dass ECMA-262 die Verwendung dieser Struktur nicht vorschreibt, sondern nur diesen Teil der Funktion beschreibt.
Nachdem wir den Prozess der Identifikatorauflösung (Identifier Resolution) verstanden haben, können wir verstehen, warum lokale Variablen schneller aufgelöst werden als Variablen in anderen Bereichen, hauptsächlich weil der Suchprozess stark verkürzt wird. Aber wie viel schneller wird es sein? Um diese Frage zu beantworten, habe ich eine Reihe von Tests simuliert, um die Leistung von Variablen in unterschiedlichen Umfangstiefen zu testen.
Der erste Test besteht darin, den einfachsten Wert in eine Variable zu schreiben (hier wird der Literalwert 1 verwendet). Das Ergebnis ist in der folgenden Abbildung dargestellt, was sehr interessant ist:
Aus den Ergebnissen ist nicht schwer zu erkennen, dass es zu einem Leistungsverlust kommt, wenn der Identifikator-Analyseprozess eine tiefe Suche erfordert, und der Grad des Leistungsverlusts nimmt mit zunehmender Identifikatortiefe zu. Wenig überraschend schnitt der Internet Explorer am schlechtesten ab (aber fairerweise muss man sagen, dass es in IE 8 einige Verbesserungen gab). Es ist erwähnenswert, dass es hier einige Ausnahmen gibt: Google Chrome und die neueste Mitternachtsversion von WebKit haben eine sehr stabile Zugriffszeit auf Variablen und erhöhen sich nicht mit zunehmender Bereichstiefe. Dies ist natürlich auf die von ihnen verwendeten JavaScript-Engines der nächsten Generation, V8 und SquirrelFish, zurückzuführen. Diese Engines führen bei der Codeausführung Optimierungen durch, und es ist klar, dass diese Optimierungen den Zugriff auf Variablen schneller als je zuvor machen. Opera schnitt ebenfalls gut ab und war viel schneller als IE, Firefox und die aktuelle Version von Safari, aber langsamer als Browser auf Basis von V8 und Squirrelfish. Die Leistung von Firefox 3.1 Beta 2 ist etwas unerwartet. Die Ausführungseffizienz lokaler Variablen ist sehr hoch, aber mit zunehmender Anzahl der Bereichsebenen nimmt die Effizienz stark ab. Es ist zu beachten, dass ich hier die Standardeinstellungen verwende, was bedeutet, dass Firefox die Trace-Funktion nicht aktiviert hat.
Die oben genannten Ergebnisse wurden durch die Ausführung von Schreiboperationen an Variablen erzielt. Tatsächlich war ich neugierig, ob die Situation beim Lesen von Variablen anders sein würde, also habe ich den folgenden Test durchgeführt. Es wurde festgestellt, dass die Lesegeschwindigkeit etwas höher ist als die Schreibgeschwindigkeit, der Trend zu Leistungsänderungen ist jedoch konsistent.
Wie im vorherigen Test waren Internet Explorer und Firefox immer noch die langsamsten, und Opera zeigte eine sehr auffällige Leistung. Ebenso zeigten Chrome und die neueste Version von Webkit Midnight Edition Leistungstrends, die nichts mit der Umfangstiefe zu tun haben Es lohnt sich, darauf zu achten. Ja, die variable Zugriffszeit in Firefox 3.1 Beta 2 weist immer noch einen seltsamen Tiefensprung auf.
Während des Tests habe ich ein interessantes Phänomen entdeckt, nämlich dass Chrome beim Zugriff auf globale Variablen zusätzliche Leistungseinbußen erleiden wird. Die Zeit für den Zugriff auf globale Variablen hat nichts mit der Bereichsebene zu tun, sie ist jedoch 50 % länger als die Zeit für den Zugriff auf lokale Variablen derselben Ebene.
Welche Erkenntnisse können uns diese beiden Tests bringen? Die erste besteht darin, den alten Standpunkt zu überprüfen, der darin besteht, so viele lokale Variablen wie möglich zu verwenden. In allen Browsern ist der Zugriff auf lokale Variablen schneller als der bereichsübergreifende Zugriff auf Variablen, einschließlich globaler Variablen. Die folgenden Punkte sollten die durch diesen Test gesammelten Erfahrungen sein:
* Überprüfen Sie sorgfältig alle in der Funktion verwendeten Variablen. Wenn es eine Variable gibt, die im aktuellen Bereich nicht definiert ist und mehr als einmal verwendet wird, sollten wir diese Variable in a speichern Lokale Variable: Verwenden Sie diese lokale Variable, um Lese- und Schreibvorgänge auszuführen. Dies kann uns helfen, die Suchtiefe von Variablen außerhalb des Gültigkeitsbereichs auf 1 zu reduzieren. Dies ist besonders wichtig für globale Variablen, da globale Variablen immer an der letzten Position der Gültigkeitsbereichskette durchsucht werden.
* Vermeiden Sie die Verwendung der with-Anweisung. Weil dadurch die Bereichskette des Ausführungskontexts (Ausführungskontext) geändert und vorne ein Objekt (Variablenobjekt) hinzugefügt wird. Dies bedeutet, dass während der Ausführung von with die tatsächlichen lokalen Variablen an die zweite Position in der Bereichskette verschoben werden, was zu einem Leistungsverlust führt.
* Wenn Sie sicher sind, dass ein Codeabschnitt definitiv eine Ausnahme auslöst, vermeiden Sie die Verwendung von Try-Catch, da der Catch-Zweig in derselben Bereichskette wie bei verarbeitet wird. Es gibt jedoch keinen Leistungsverlust im Try-Branch-Code, daher wird dennoch empfohlen, Try-Catch zu verwenden, um unvorhersehbare Fehler abzufangen.
Wenn Sie mehr Diskussion zu diesem Thema wünschen, habe ich letzten Monat beim Mountain View JavaScript Meetup einen kleinen Vortrag gehalten. Sie können die Folien auf SlideShare herunterladen oder sich das vollständige Video der Party ansehen, die etwa nach 11 Minuten meines Vortrags beginnt.
Anmerkungen des Übersetzers:
Wenn Sie beim Lesen dieses Artikels Zweifel haben, empfehle ich Ihnen, die folgenden beiden Artikel zu lesen:
* „JavaScript Object Model-Execution Model“, geschrieben von Richie
* „ECMA-262 Third Edition“, schauen Sie sich hauptsächlich Kapitel 10 an, das den Ausführungskontext darstellt. Die in diesem Artikel erwähnten Begriffe werden dort ausführlich erläutert.
Am Ende erwähnte Nicholas ein Mountain View-JavaScript-Meetup. Die Meetup-Website ist eigentlich eine Organisationswebsite für verschiedene reale Aktivitäten. Das Leben in Kalifornien ist wirklich ein Segen Aktivitäten zum Mitmachen. hehe.