Anmerkung des Übersetzers: Ich bin das erste Mal, dass ich eine Fremdsprache übersetzt habe, und meine Worte sind unweigerlich etwas dunkel, aber ich habe mein Bestes versucht, die ursprüngliche Absicht des Autors auszudrücken und hatte nicht viel Politur. Kritik und Korrektur sind willkommen. Darüber hinaus ist dieser Artikel lang und verfügt über eine große Menge an Informationen, die schwer zu verdauen sind. Bitte hinterlassen Sie eine Nachricht, um Details zu besprechen. Dieser Artikel konzentriert sich hauptsächlich auf die Leistungsoptimierung von V8, und ein Teil des Inhalts ist nicht für alle JS -Motoren anwendbar. Bitte geben Sie bitte die Quelle beim Nachdruck an :)
===========================================================================
Viele JavaScript -Motoren, wie die V8 -Engine von Google (verwendet von Chrome und Node), sind speziell für große JavaScript -Anwendungen entwickelt, die eine schnelle Ausführung erfordern. Wenn Sie Entwickler sind und sich über Speicherverbrauch und Seitenleistung besorgt haben, sollten Sie verstehen, wie die JavaScript -Engine in Ihrem Browser funktioniert. Egal, ob es sich um V8, Spidermonkey (Firefox) Carakan (Opera), Chakra (dh) oder andere Motoren handelt, dies kann Ihnen helfen , Ihre Anwendung besser zu optimieren . Dies bedeutet nicht, dass Sie speziell für einen bestimmten Browser oder Motor optimieren sollten und dies niemals tun sollten.
Sie sollten sich jedoch ein paar Fragen stellen:
Eine schnelle Lade -Website ist wie ein schneller Sportwagen, für den speziell angepasste Teile erforderlich sind. Bildquelle: Dhybridcars.
Es gibt einige gemeinsame Fallstricke beim Schreiben von Hochleistungscode, und in diesem Artikel werden wir einige nachgewiesene und bessere Möglichkeiten zeigen, Code zu schreiben.
Wenn Sie kein tiefes Verständnis für JS -Motoren haben, gibt es kein Problem damit, eine große Webanwendung zu entwickeln, genau wie eine Person, die fahren kann, nur die Motorhaube, aber nicht den Motor in der Motorhaube gesehen hat. Angesichts der Tatsache, dass Chrome die erste Wahl meines Browsers ist, sprechen wir über seine JavaScript -Engine. V8 besteht aus den folgenden Kernteilen:
Die Müllsammlung ist eine Form der Speicherverwaltung , die eigentlich ein Konzept eines Kollektors ist und versucht, das Gedächtnis zu recyceln, das von Objekten besetzt ist, die nicht mehr verwendet werden. In einer Müllsammlungssprache wie JavaScript werden Objekte, auf die noch in der Anwendung verwiesen wird, nicht gelöscht.
In den meisten Fällen ist nicht erforderlich, dass Objektreferenzen manuell beseitigen. Alles funktioniert gut, indem sie einfach die Variablen dorthin legen, wo sie benötigt werden (idealerweise so lokal wie möglich, d. H. Die Funktion, die sie anstelle der äußeren Schicht der Funktion verwendet werden).
Der Müllsammler versucht das Gedächtnis zu recyceln. Bildquelle: Valtteri Mäki.
In JavaScript ist es unmöglich, die Müllsammlung zu erzwingen. Sie sollten dies nicht tun, da der Müllsammlungsprozess von der Laufzeit gesteuert wird und weiß, was der beste Zeitpunkt ist, um aufzuräumen.
Es gibt viele Diskussionen über das Recycling von JavaScript -Speicher im Internet zum Löschen von Keywords. Obwohl es verwendet werden kann, um Attribute (Tasten) in Objekten (MAP) zu löschen, glauben einige Entwickler, dass es verwendet werden kann, um "Dereferenzen" zu erzwingen. Es wird empfohlen, nach Möglichkeit das Löschen zu vermeiden. Im folgenden Beispiel delete ox 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
var o = {x: 1}; Ochsen löschen; // wahrer Ochsen; // undefiniertSie werden leicht die Referenzentfernung in beliebten JS -Bibliotheken finden - dies ist sprachlich zielgerichtet. Es ist hier zu beachten, dass es vermeiden sollte, die Struktur des "heißen" Objekts zur Laufzeit zu ändern. Die JavaScript -Engine kann solche "heißen" Objekte erkennen und versuchen, sie zu optimieren. Wenn sich die Struktur des Objekts während des Lebenszyklus nicht wesentlich ändert, ist der Motor für die Optimierung des Objekts leichter, und der Löschvorgang wird tatsächlich diese größere strukturelle Änderung auslösen, was der Optimierung des Motors nicht förderlich ist.
Es gibt auch Missverständnisse darüber, wie Null funktioniert. Wenn Sie ein Objektreferenz auf NULL festlegen, wird das Objekt nicht "leer", sondern legt nur seinen Verweis auf leer fest. Die Verwendung von OX = NULL ist besser als die Verwendung von Löschen, es ist jedoch möglicherweise nicht erforderlich.
var o = {x: 1}; o = null; o; // nullo.x // typeerrorWenn diese Referenz die letzte Referenz auf das aktuelle Objekt ist, wird das Objekt Müll erhoben. Wenn diese Referenz nicht der letzte Verweis auf das aktuelle Objekt ist, ist das Objekt zugänglich und ist nicht Müll gesammelt.
Es sollte auch beachtet werden, dass globale Variablen während des Lebenszyklus der Seite nicht vom Müllsammler gereinigt werden. Unabhängig davon, wie lange die Seite geöffnet ist, existieren Variablen im Bereich des globalen Objekts immer, wenn das JavaScript ausgeführt wird.
var myGlobalnameSpace = {};Globale Objekte werden nur gereinigt, wenn Sie die Seite aktualisieren, zu einer anderen Seite navigieren, die Registerkarte schließen oder den Browser verlassen. Variablen im Funktionsumfang werden gereinigt, wenn sie aus dem Spielraum heraus sind, dh wenn die Funktion ausgeht, gibt es keine Referenzen, und solche Variablen werden gereinigt.
Damit der Müllsammler so viele Objekte wie möglich sammelt, halten Sie keine Objekte, die nicht mehr verwendet werden . Hier sind ein paar Dinge, an die man sich erinnern muss:
Lassen Sie uns als nächstes über Funktionen sprechen. Wie wir bereits gesagt haben, funktioniert die Müllsammlung, indem sie Speicherblöcke (Objekte) recyceln, die nicht mehr zugänglich sind. Um dies besser zu veranschaulichen, finden Sie hier einige Beispiele.
Funktion foo () {var bar = new LigObject (); bar.somecall ();}Wenn FOO zurückkehrt, wird das von der Balk bezeichnete Objekt vom Müllsammler automatisch recycelt, da es keine vorhandenen Referenzen enthält.
Vergleichen:
Funktion foo () {var bar = new LigObject (); bar.somecall (); Rückgabeleiste;} // wohinevar b = foo ();Jetzt haben wir eine Referenz, die auf das Balkenobjekt zeigt, so dass der Lebenszyklus des Balkenobjekts vom Anruf nach Foo fortgesetzt wird, bis der Anrufer eine andere Variable B angibt (oder B ist aus dem Zielfernrohr nicht mehr).
Wenn Sie eine Funktion sehen, geben Sie eine interne Funktion zurück, die auch nach der Ausführung der externen Funktion aus dem Zielfernsehzugriff herauskommt. Dies ist ein grundlegender Verschluss - ein Ausdruck von Variablen, die in einem bestimmten Kontext festgelegt werden können. Zum Beispiel:
Funktionsumme (x) {Funktion sumit (y) {return x + y; }; return sumit;} // useagevar suma = sum (4); var sumb = suma (3); console.log (Sumb); // Gibt 7 zurückDas im Summenaufrufkontext generierte Funktionsobjekt (SUMIT) kann nicht recycelt werden. Es wird durch die globale Variable (SUMA) verwiesen und kann durch Suma (n) aufgerufen werden.
Schauen wir uns ein anderes Beispiel an, wo wir auf die variable Größen zugreifen können?
var a = function () {var frearstr = new Array (1000000) .Join ('x'); return function () {return lvestr; };} ();Ja, wir können über einen () auf Grarstr zugreifen, sodass es nicht recycelt wird. Was ist mit Folgendes?
var a = function () {var smallstr = 'x'; var frearstr = neuarray (1000000) .Join ('x'); Rückgabefunktion (n) {return smallstr; };} ();Wir können keinen Zugang mehr auf Gratreal, sondern bereits ein Kandidat für die Müllabfuhr haben. [Anmerkung des Übersetzers: Weil Grenstr keine externen Referenzen mehr hat]
Einer der schlimmsten Orte zum Leakspeicher ist in der Schleife oder in setTimeout ()/setInterval (), aber dies ist weit verbreitet. Denken Sie an die folgenden Beispiele:
var myobj = {callMemaybe: function () {var myref = this; var val = setTimeout (function () {console.log ('Zeit ist ausfällt!'); MyRef.CallMemaybe ();}, 1000); }}; Wenn wir myobj.callmemaybe () ausführen; Um den Timer zu starten, können wir sehen, dass die Konsole "Zeit ausgeht!" jede Sekunde. Wenn myObj = null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。
Es ist auch zu erinnern, dass Referenzen in SetTimeout/SetInterval -Aufrufen (z. B. Funktionen) ausgeführt und abgeschlossen werden müssen, bevor sie Müll gesammelt werden können.
Optimieren Sie den Code niemals, bis Sie ihn wirklich brauchen. Jetzt können Sie oft einige Benchmarks sehen, die zeigen, dass n in V8 als m optimiert ist. Wenn Sie es jedoch in Modulcode oder Anwendung testen, werden Sie feststellen, dass diese Optimierungen wirklich viel kleiner sind als Sie erwartet.
Es ist besser, nichts als zu viel zu tun. Bildquelle: Tim Sheerman Chase.
Zum Beispiel möchten wir ein solches Modul erstellen:
Es gibt verschiedene Faktoren in diesem Problem, obwohl es auch leicht zu lösen ist. Wie speichern wir Daten, wie zeichnen wir effizient Tabellen und fügen sie an das DOM hinzu und wie können wir besser mit Tabellenereignissen umgehen?
Der anfängliche (naive) Ansatz für diese Probleme besteht darin, Objekte zu verwenden, um Daten zu speichern und in ein Array zu versetzen, JQuery zu verwenden, um die Daten zu durchqueren, um eine Tabelle zu zeichnen und sie an das DOM anzuhängen, und schließlich die Ereignisbindung an das von uns erwartete Klickverhalten zu verwenden.
Hinweis: Dies ist nicht das, was Sie tun sollten
var modulea = function () {return {data: dataArrayObject, init: function () {this.addtable (); this.adDevent (); }, addtable: function () {for (var i = 0; i <Zeilen; i ++) {$ tr = $ ('<tr> </tr>'); für (var j = 0; j <thata.length; j ++) {$ tr.Append ('<td>' + that.data [j] ['id'] + '</td>'); } $ tr.Appendto ($ tbody); }}, addEvents: function () {$ ('Tabelle TD'). on ('click', function () {$ (this) .TogGleClass ('Active');}); }};} ();Dieser Code vervollständigt die Aufgabe einfach und effektiv.
In diesem Fall sind die Daten, die wir durchqueren, nur eine numerische Eigenschafts -ID, die einfach im Array aufbewahrt werden sollte. Interessanterweise ist es besser, DocumentFragment- und Lokal -DOM -Methoden direkt zu verwenden, als Tabellen mit JQuery (auf diese Weise) zu generieren, und natürlich hat Ereignisproxy eine höhere Leistung als die Bindung jeder TD allein.
Beachten Sie, dass JQuery zwar in unserem Beispiel Dokumentfragment verwendet, in unserem Beispiel die Codeaufrufe in einer Schleife anhängen und diese Anrufe ein anderes kleines Wissen beinhalten, sodass der Optimierungseffekt hier nicht sehr gut ist. Hoffentlich wird dies kein Schmerzpunkt sein, aber stellen Sie sicher, dass Sie einen Benchmark durchführen, um sicherzustellen, dass Ihr Code in Ordnung ist.
Für unser Beispiel bringen die oben genannten Praktiken (gewünschte) Leistungsverbesserungen mit. Event Proxying ist eine Verbesserung der einfachen Bindung, und die optionale DocumentFragment hilft auch.
var moduled = function () {return {data: dataArray, init: function () {this.addtable (); this.adDevent (); }, addtable: function () {var td, tr; var fragment = document.createdocumentfragment (); var fragment2 = document.createdocumentfragment (); für (var i = 0; i <Zeilen; i ++) {tr = document.createelement ('tr'); für (var j = 0; j <thata.data.length; j ++) {td = document.createelement ('td'); td.AppendChild (document.CreateTextNode (this.data [j])); Frag2.AppendChild (TD); } tr.AppendChild (Frag2); Frag.AppendChild (tr); } tbody.AppendChild (Frag); }, addEvents: function () {$ ('table'). on ('click', 'td', function () {$ (this) .ToggleClass ('active');}); }};} ();Schauen wir uns andere Möglichkeiten an, um die Leistung zu verbessern. Möglicherweise haben Sie gelesen, dass die Verwendung des Prototypenmodus besser ist als der Modulmodus oder Sie haben gehört, dass die Verwendung von JS -Vorlagen -Frameworks besser funktioniert. Manchmal gilt dies, aber sie werden verwendet, um den Code lesbarer zu machen. Übrigens gibt es Vorkompilierung! Mal sehen, wie es in der Praxis funktioniert?
moduleg = function () {}; moduleg.prototype.data = dataArray; moduleg.prototype.init = function () {this.addtable (); this.adDevents ();}; moduleg.prototype.addtable = function () {var template = _.template ($ ('#Vorlage'). text ()); var html = template ({'data': this.data}); $ tbody.Append (html);}; moduleg.prototype.adDevents = function () {$ ('table'). on ('click', 'td', function () {$ (this) .togGleClass ('active');});}; var modg = new moduleg ();Es stellt sich heraus, dass die Leistungsverbesserungen in dieser Situation vernachlässigbar sind. Die Wahl der Vorlagen und Prototypen bietet nichts mehr. Das heißt, Leistung ist nicht der Grund, warum Entwickler sie verwenden, und die Lesbarkeit, das Erbmodell und die Wartbarkeit, die dem Code gebracht werden, sind die wirklichen Gründe.
Zu den komplexeren Problemen gehören effizientes Zeichnen von Bildern auf Leinwand und das Manipulieren von Pixeldaten mit oder ohne Arrays von Typen.
Erfahren Sie vor dem Einsatz einiger Methoden für Ihre eigene Anwendung mehr über das Benchmarking dieser Lösungen. Vielleicht erinnert sich jemand immer noch an den Schießen und die nachfolgenden Erweiterungen der JS-Vorlage. Sie müssen herausfinden, dass das Benchmarking in virtuellen Anwendungen, die Sie nicht nicht sehen können, nicht vorhanden sind, sondern die Optimierungen, die durch Ihren tatsächlichen Code mitgebracht wurden, testen sollten.
Die Optimierungspunkte jedes V8 -Motors werden ausführlich außerhalb des Geltungsbereichs dieses Artikels eingeführt. Natürlich gibt es hier viele Tipps, die es wert sind, hier zu erwähnen. Denken Sie an diese Tipps und Sie können den Code reduzieren, der eine schlechte Leistung hat.
Funktion add (x, y) {return x+y;} add (1, 2); add ('a', 'b'); add (my_custom_object, undefiniert);Weitere Inhalte finden Sie in der Freigabe von Daniel Clifford auf Google I/O. Das JavaScript -Geschwindigkeitsbegrenzung mit V8 brechen. Optimierung für V8 - Eine Serie ist ebenfalls sehr wert.
Es gibt nur einen Hauptunterschied zwischen Objekten und Arrays in JavaScript, dh der magischen Länge Eigenschaft von Arrays. Wenn Sie diese Eigenschaft selbst unterhalten, sind Objekte und Arrays in V8 so schnell wie in Arrays.
Das Klonen von Objekten ist ein häufiges Problem für Anwendungsentwickler. Während verschiedene Benchmarks beweisen können, dass V8 dieses Problem gut behandelt, seien Sie vorsichtig. Das Kopieren großer Dinge ist normalerweise langsamer - tun Sie das nicht. Die für... in der Schleife in JS ist besonders schlecht, weil es eine dämonische Spezifikation hat und möglicherweise nie schneller ist als jedes Objekt in einem Motor.
Wenn Sie sicher Objekte auf den kritischen Leistungscodepfad kopieren, verwenden Sie ein Array oder eine benutzerdefinierte Funktion "Konstruktor kopieren", um jede Eigenschaft explizit zu kopieren. Dies ist wahrscheinlich der schnellste Weg:
Funktionsklone (Original) {this.foo = original.foo; this.bar = original.bar;} var copy = new clone (original);Zwischenspeicherungsfunktionen bei Verwendung des Modulmodus können zu Leistungsverbesserungen führen. Siehe das Beispiel unten, da es immer eine neue Kopie der Mitgliedsfunktion erstellt, die Änderungen, die Sie sehen, möglicherweise langsamer sein.
Beachten Sie auch, dass die Verwendung dieser Methode offensichtlich besser ist und sich nicht nur auf den Prototypenmodus stützt (bestätigt durch JSPERF -Test).
Leistungsverbesserungen bei Verwendung des Modulmodus oder des Prototypmodus
Dies ist ein Leistungsvergleichstest im Prototypenmodus und des Modulmodus:
// Prototyp -Muster kLass1 = function () {} klass1.prototype.foo = function () {log ('foo'); } Klass1.prototype.bar = function () {log ('bar'); } // Modulmuster kLass2 = function () {var foo = function () {log ('foo'); }, bar = function () {log ('bar'); }; return {foo: foo, bar: bar}} // Modulmuster mit zwischengespeicherten Funktionen var foofunction = function () {log ('foo'); }; var barfunction = function () {log ('bar'); }; KLASS3 = function () {return {foo: foofUction, balk: barfunction}} // Iterationstests // prototypale var i = 1000, objs = []; while (i--) {var o = new klass1 () objs.push (new klass1 ()); O.BAR; O.Foo; } // Modulmuster var i = 1000, objs = []; while (i--) {var o = new klass1 () objs.push (new klass1 ()); O.BAR; O.Foo; } // Modulmuster var i = 1000, objs = []; while (i--) {var o = klass2 () objs.push (klass2 ()); O.BAR; O.Foo; } // Modulmuster mit zwischengespeicherten Funktionen var i = 1000, objs = []; while (i--) {var o = klass3 () objs.push (klass3 ()); O.BAR; O.Foo; } // Weitere Informationen finden Sie im TestLassen Sie uns als nächstes über die Techniken im Zusammenhang mit Arrays sprechen. Löschen Sie im Allgemeinen keine Array -Elemente , wodurch der Array -Übergang zu einer langsameren internen Darstellung übergeht. Wenn der Index spärlich wird, verwandelt V8 das Element in ein langsameres Wörterbuchmuster.
Array -Literale sind sehr nützlich und können auf die Größe und den Typ des VM -Arrays hinweisen. Es wird normalerweise in Arrays mit kleinen Größen verwendet.
// Hier kann V8 sehen, dass Sie ein 4-Element-Array mit Zahlen haben möchten: var a = [1, 2, 3, 4]; // Tu dies nicht: a = []; // Hier weiß V8 nichts über das Arrayfor (var i = 1; i <= 4; i ++) {a.push (i);}Es ist keineswegs eine gute Idee, Daten von gemischten Typen (z. B. Zahlen, Zeichenfolgen, undefiniert, wahr/falsch) in einem Array zu speichern. Zum Beispiel var arr = [1, „1“, undefiniert, wahr, „wahr“]
Leistungsprüfung der Typinferenz
Wie wir gesehen haben, sind Arrays von Ganzzahlen am schnellsten.
Wenn Sie spärliche Arrays verwenden, achten Sie auf die Zugriffselemente und sind viel langsamer als vollständige Arrays. Weil V8 kein ganzes Stück Platz für ein Array zuteilt, das nur einen Teil des Raums verwendet. Stattdessen wird es in einem Wörterbuch verwaltet, das sowohl Platz sparen als auch Zeit zum Zugriff.
Testen von spärlichen Arrays und vollständigen Arrays
Nicht vorhandene große Arrays (z. B. Elemente größer als 64.000), ihre maximale Größe, sollte aber dynamisch zugewiesen werden. Denken Sie vor dem Testen unserer Leistung in diesem Artikel daran, dass dies nur für einige JavaScript -Motoren gilt.
Leere Literale und vorab zugeordnete Arrays werden in verschiedenen Browsern getestet
Nitro (Safari) ist für vorab erneuerte Arrays vorteilhafter. In anderen Motoren (V8, Spidermonkey) ist die Voranierung nicht effizient.
Preallocated Array -Tests
// leer arrayvar arr = []; für (var i = 0; i <1000000; i ++) {arr [i] = i;} // Vorgewachsene Arrayvar arr = new Array (1000000); für (var i = 0; i <1000000; i ++) {arr [i] = i;}In der Welt der Webanwendungen ist Geschwindigkeit alles. Kein Benutzer möchte eine Tabellenanwendung verwenden, die Sekunden dauert, um die Gesamtzahl einer Spalte zu berechnen oder Informationen zusammenzufassen. Dies ist ein wichtiger Grund, warum Sie jede Leistung in Ihrem Code drücken möchten.
Bildquelle: pro Olof Forsberg.
Es ist sehr nützlich, die Leistung Ihrer Anwendung zu verstehen und zu verbessern, aber auch schwierig. Wir empfehlen die folgenden Schritte, um Leistungspunkte zu lösen:
Einige der unten empfohlenen Werkzeuge und Techniken können Ihnen helfen.
Es gibt viele Möglichkeiten, einen Benchmark für JavaScript -Code -Snippets auszuführen, um seine Leistung zu testen. Die allgemeine Annahme ist, dass der Benchmark einfach zwei Zeitstempel vergleicht. Dieses Muster wird vom JSPERF -Team hingewiesen und in der Benchmark -Suite von Sunspider und Kraken verwendet:
var TotalTime, start = neues Datum, iterations = 1000; while (iterations--) {// Code-Snippet geht hierher} // TotalTime → Die Anzahl der Millionensekunden // zum Ausführen des Code-Snippet 1000 TimestotalTime = New Date-Start;Hier wird der zu testende Code in eine Schleife platziert und führt eine festgelegte Anzahl von Male aus (z. B. 6 Mal). Danach wird das Startdatum vom Enddatum abgezogen, und die Zeit, die für die Durchführung der Operation in der Schleife benötigt wird, wird abgeleitet.
Dieses Benchmarking ist jedoch zu einfach, insbesondere wenn Sie Benchmarks in mehreren Browsern und Umgebungen ausführen möchten. Der Müllsammler selbst hat einen gewissen Einfluss auf die Ergebnisse. Selbst wenn Sie eine Lösung wie Fenster verwenden. Performance, müssen diese Mängel berücksichtigt werden.
Unabhängig davon, ob Sie den Benchmark -Teil des Codes nur ausführen, eine Testsuite schreiben oder die Benchmark -Bibliothek codieren, sind JavaScript -Benchmarks tatsächlich mehr als Sie denken. Für detailliertere Guide-Benchmarks empfehle ich Ihnen dringend, die von Mathias Bynens und John-David Dalton bereitgestellten JavaScript-Benchmarks zu lesen.
Chrome Developer Tools unterstützen eine gute Unterstützung für JavaScript -Analysen. Sie können diese Funktion verwenden, um festzustellen, welche Funktionen die meiste Zeit in Anspruch nehmen, damit Sie diese optimieren können. Dies ist wichtig, selbst kleine Änderungen im Code können erhebliche Auswirkungen auf die Gesamtleistung haben.
Chrome Developer Tools Analyse Panel
Der Analyseprozess beginnt, die Basis der Codeleistung zu erhalten, und manifestiert sie dann in Form einer Zeitleiste. Dies wird uns sagen, wie lange der Code dauern wird, um zu laufen. Die Registerkarte "Profile" gibt uns eine bessere Perspektive auf das, was in der Anwendung vor sich geht. JavaScript -CPU -Analysedateien zeigen, wie viel CPU -Zeit in unserem Code verwendet wird, CSS -Selektoranalysedateien zeigen, wie viel Zeit für Verarbeitungsauswahlern aufgewendet wird, und Heap -Snapshots zeigen, wie viel Speicher in unseren Objekten verwendet wird.
Mit diesen Tools können wir trennen, anpassen und wieder analysieren, um zu messen, ob unsere funktionalen oder operativen Leistungsoptimierungen tatsächlich wirksam sind.
Auf der Registerkarte "Profil" wird die Code -Leistungsinformationen angezeigt.
Lesen Sie die JavaScript -Profiling von Zack Grossbart mit den Chrome Developer Tools eine gute Einführung in die Analyse.
Tipp: Wenn Sie sicherstellen möchten, dass Ihre Analyse nicht von installierten Anwendungen oder Erweiterungen beeinflusst wird, können Sie das Flag --user-data-dir <empty_directory> verwenden, um Chrome zu starten. In den meisten Fällen sollte dieser Methodenoptimierungstest ausreichend sein, dauert jedoch auch mehr Zeit für Sie. Dies kann das V8 -Logo helfen.
In Google werden Chrome -Entwickler -Tools von Teams wie Google Mail häufig verwendet, um Speicherlecks zu erkennen und zu beheben.
Speicherstatistiken in Chromentwickler -Tools
Speicher zählt die private Speicherverwendung, die Größe des JavaScript -Haufens, die Anzahl der DOM -Knoten, die Speicherung von Speichern, Ereignishörzähler und Müllsammler, über die unser Team besorgt ist. Empfohlenes Lesen von Loreena Lees "3 Snapshot" -Technologie. Der wichtigste Punkt dieser Technik besteht darin, ein Verhalten in Ihrer Anwendung zu protokollieren, die Müllsammlung zu erzwingen, zu prüfen, ob die Anzahl der Dom -Knoten in die erwartete Grundlinie wiederhergestellt wurde, und dann die Schnappschüsse der drei Haufen analysieren, um festzustellen, ob ein Speicherleck vorliegt.
Speicherverwaltung einzelner Seitenanwendungen (wie AngularJs, Backbone, Ember) ist sehr wichtig, sie aktualisieren die Seite fast nie. Dies bedeutet, dass Speicherlecks ziemlich offensichtlich sein können. Einseitige Anwendungen in mobilen Terminals sind voller Fallstricke, da das Gerät über einen begrenzten Speicher verfügt und Anwendungen wie E-Mail-Clients oder soziale Netzwerke für lange Zeit ausführt. Je größer die Fähigkeit, desto schwerer die Verantwortung.
Es gibt viele Möglichkeiten, dieses Problem zu lösen. Stellen Sie bei Backbone sicher, dass Sie Dispose () verwenden, um alte Ansichten und Referenzen (derzeit in Backbone verfügbar) zu verarbeiten. Diese Funktion wird kürzlich hinzugefügt, wobei der Handler zum "Ereignis" -Orententjekt der Ansicht hinzugefügt wird, und der Ereignishörer durch das Modell oder die Sammlung des dritten Parameters (Rückrufkontext), die an die Aussicht übergeben wurden. Der Hörer, der Speicherlecks vermeidet, wenn er feststellt, dass Elemente entfernt werden.
Einige kluge Ratschläge von Derick Bailey:
Anstatt zu verstehen, wie Ereignisse und Referenzen funktionieren, befolgen Sie die Standardregeln, um den Speicher in JavaScript zu verwalten. Wenn Sie Daten in eine Backbone -Sammlung voller Benutzerobjekte laden möchten, möchten Sie die Sammlung so löschen, dass sie nicht mehr Speicher aufnimmt. Alle Verweise auf die Sammlung und Verweise auf die Objekte in der Sammlung sind erforderlich. Sobald die verwendete Referenz klar ist, wird die Ressource recycelt. Dies sind die Standardregeln für JavaScript -Müllsammlungen.
In dem Artikel deckt Derick bei Verwendung von Backbone.js viele häufige Speicherfehler ab und wie diese Probleme lösen.
Das Tutorial zum Debuggen von Speicherlecks im Knoten von Felix Geisendörfer ist ebenfalls gelesen, insbesondere wenn es Teil eines breiteren Spa -Stacks ist.
Wenn der Browser Elemente in einem Dokument neu zurückgeführt hat, müssen sie neu berechnet werden und ihre Positionen und Geometrie, die wir Refews nennen. Reflow blockiert die Vorgänge der Benutzer im Browser. Es ist daher sehr hilfreich zu verstehen, dass die Verbesserung der Reflow -Zeit verbessert wird.
Reflow -Zeit -Diagramm
Sie sollten Reflow oder neu zeichnen in Stapeln auslösen, diese Methoden jedoch in Maßen verwenden. Es ist auch wichtig zu versuchen, nicht mit DOM umzugehen. Sie können DocumentFragment verwenden, ein leichtes Dokumentobjekt. Sie können es verwenden, um einen Teil des Dokumentbaums zu extrahieren oder ein neues Dokument "Fragment" zu erstellen. Anstatt ständig DOM -Knoten hinzuzufügen, ist es besser, DOM -Einfügungsvorgänge nur einmal nach der Verwendung des Dokumentfragments durchzuführen, um übermäßigen Reflow zu vermeiden.
Zum Beispiel schreiben wir eine Funktion, um einem Element 20 Divs hinzuzufügen. Wenn Sie jedes Mal ein DIV an das Element anhängen, wird 20 Reflexe ausgelöst.
Funktion addDivs (Element) {var div; für (var i = 0; i <20; i ++) {div = document.createelement ('div'); div.innerhtml = 'Heya!'; Element.AppendChild (Div); }}Um dieses Problem zu lösen, können wir stattdessen eine Dokumentfragment verwenden. Wir können es jeweils eine neue DIV hinzufügen. Durch das Hinzufügen von DocumentFagment zum DOM nach Abschluss wird nur einmal ein Reflow ausgelöst.
Funktion addDivs (Element) {var div; // Erstellt eine neue leere Dokumentfragment. var fragment = document.createdocumentfragment (); für (var i = 0; i <20; i ++) {div = document.createelement ('a'); div.innerhtml = 'Heya!'; Fragment.AppendChild (Div); } element.appendChild (Fragment);}Sehen Sie das Web schneller, die Optimierung von JavaScript -Speicher und das Finden von Speicherlecks.
Um JavaScript -Speicherlecks zu entdecken, entwickelten Google -Entwickler (Marja Hölttä und Jochen Eisinger) ein Tool, das in Verbindung mit Chromentwickler -Tools arbeitet, um Schnappschüsse des Heaps abzurufen und festzustellen, welche Objekte das Speicherleck verursachen.
Ein Tool zur Erkennung von JavaScript -Speicher und Leck
Es gibt einen vollständigen Artikel über die Verwendung dieses Tools. Es wird empfohlen, dass Sie sich selbst zur Seite des Speicher -Leck -Detektorprojekts wenden.
Wenn Sie wissen möchten, warum solche Tools nicht in unsere Entwicklungstools integriert wurden, gibt es zwei Gründe. Es wurde ursprünglich so konzipiert, dass wir einige spezifische Speicherszenarien in der Verschlussbibliothek erfassen können, was eher als externes Werkzeug geeignet ist.
Chrome unterstützt das Übergeben einiger Flags direkt an V8, um detailliertere Ergebnisse der Motoroptimierung auszugeben. Dies kann beispielsweise die Optimierung von V8 verfolgen:
"/Applications/Google Chrome/Google Chrome"-Js-flags = "-Trace-opt--Trace-deopt" "
Windows-Benutzer können Chrome.exe JS-flags = "Trace-opt Trace-deopt" ausführen.
Bei der Entwicklung einer Anwendung kann das unten stehende V8 -Logo verwendet werden.
Das Verarbeitungsskript von V8 verwendet * (Asterisk), um optimierte Funktionen zu identifizieren, und ~ (Wellen-), um nicht optimierte Funktionen darzustellen.
Wenn Sie mehr über das Logo des V8 und die Funktionsweise des V8 -Innenraums erfahren möchten, wird dringend empfohlen, Vyacheslav Egorovs hervorragenden Beitrag zu V8 -Interna zu lesen.
Die Hochgenauigkeitszeit (HRT) ist eine hochpräzisen Zeitoberfläche auf Submillisekundenebene, die keinen Einfluss auf die Systemzeit und die Benutzeranpassungen hat. Es kann als genauere Messmethode angesehen werden als das neue Datum und Datum. Now (). Dies hilft uns sehr beim Schreiben von Benchmarks.
Hochvorbereitete Zeit (HRT) liefert die aktuelle Zeitgenauigkeit der Untermillisekunden
Derzeit wird HRT in Chrome (stabile Version) in window.performance.webkitnow () verwendet, aber das Präfix wird in Chrome Canary verworfen, was es ermöglicht, window.performance.now () zu rufen. Paul Irish hat mehr über HRT in HTML5Rocks gepostet.
Gibt es jetzt, da wir die aktuelle genaue Zeit kennen, eine API, die die Seitenleistung genau messen kann? Well, now there is a Navigation Timing API that provides an easy way to get accurate and detailed time measurement records when web pages are loaded and presented to users. You can use window.performance.timing in console to get time information:
显示在控制台中的时间信息
我们可以从上面的数据获取很多有用的信息,例如网络延时为responseEnd fetchStart,页面加载时间为loadEventEnd responseEnd,处理导航和页面加载的时间为loadEventEnd navigationStart。
正如你所看到的,perfomance.memory的属性也能显示JavaScript的内存数据使用情况,如总的堆大小。
更多Navigation Timing API的细节,阅读Sam Dutton的Measuring Page Load Speed With Navigation Timing。
Chrome中的about:tracing提供了浏览器的性能视图,记录了Chrome的所有线程、tab页和进程。
About:Tracing提供了浏览器的性能视图
这个工具的真正用处是允许你捕获Chrome的运行数据,这样你就可以适当地调整JavaScript执行,或优化资源加载。
Lilli Thompson有一篇写给游戏开发者的使用about:tracing分析WebGL游戏的文章,同时也适合JavaScript的开发者。
在Chrome的导航栏里可以输入about:memory,同样十分实用,可以获得每个tab页的内存使用情况,对定位内存泄漏很有帮助。
我们看到, JavaScript的世界中有很多隐藏的陷阱,且并没有提升性能的银弹。只有把一些优化方案综合使用到(现实世界)测试环境,才能获得最大的性能收益。即便如此,了解引擎是如何解释和优化代码,可以帮助你调整应用程序。
测量,理解,修复。不断重复这个过程。
图片来源: Sally Hunter
谨记关注优化,但为了便利可以舍弃一些很小的优化。例如,有些开发者选择.forEach和Object.keys代替for和for..in循环,尽管这会更慢但使用更方便。要保证清醒的头脑,知道什么优化是需要的,什么优化是不需要的。
同时注意,虽然JavaScript引擎越来越快,但下一个真正的瓶颈是DOM。回流和重绘的减少也是重要的,所以必要时再去动DOM。还有就是要关注网络,HTTP请求是珍贵的,特别是移动终端上,因此要使用HTTP的缓存去减少资源的加载。
Remembering these points can ensure that you have obtained most of the information in this article. Ich hoffe, es wird für Sie hilfreich sein!
原文:http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
作者:Addy Osmani