Im Vergleich zu C/C ++ hat die Verarbeitung von JavaScript im Speicher im von uns verwendeten JavaScript mehr Aufmerksamkeit auf das Schreiben der Geschäftslogik in der Entwicklung gelenkt. Mit der kontinuierlichen Komplexität des Unternehmens, der Entwicklung von einseitigen Anwendungen, mobilen HTML5-Anwendungen, Node.js-Programmen usw. sind die durch Speicherprobleme in JavaScript verursachten Verzögerung und Speicherüberlauf jedoch nicht mehr unbekannt geworden.
In diesem Artikel werden die Speicherverbrauch und -optimierung aus der Sprachebene von JavaScript erörtert. Von den Aspekten, mit denen jeder vertraut ist oder ein wenig gehört hat, bis hin zu den Dingen, die jeder nicht bemerken wird, werden wir sie einzeln analysieren.
1. Gedächtnisverwaltung auf Sprachebene
1.1 Umfang
Umfang ist ein sehr wichtiger Betriebsmechanismus bei der JavaScript -Programmierung. Es lenkt nicht die Aufmerksamkeit der Anfänger in der synchronen JavaScript -Programmierung auf, aber in der asynchronen Programmierung sind gute Fähigkeiten zur Kontrolle der Bereiche zu einer notwendigen Fähigkeit für JavaScript -Entwickler geworden. Darüber hinaus spielt Scope eine entscheidende Rolle in der JavaScript -Speicherverwaltung.
In JavaScript können Funktionen aufgerufen werden, mit Aussagen und globalen Scopes, die Scoped werden können.
Wie im folgenden Code als Beispiel gezeigt:
Die Codekopie lautet wie folgt:
var foo = function () {
var local = {};
};
foo ();
console.log (lokal); // => undefiniert
var bar = function () {
local = {};
};
Bar();
console.log (lokal); // => {}
Hier definieren wir die Funktion foo () und die bar () -Funktion. Ihre Absicht ist es, eine Variable namens lokal zu definieren. Das Endergebnis ist jedoch völlig anders.
In der Funktion foo () verwenden wir die VAR -Anweisung, um zu erklären, dass eine lokale Variable definiert ist. Da sich in der Funktionskörper ein Zielfernrohr bildet, wird diese Variable im Bereich definiert. Darüber hinaus gibt es in der Funktion foo () keine Bereiche Erweiterungsverarbeitung, sodass die lokale Variable auch nach der Ausführung der Funktion zerstört wird. Diese Variable kann jedoch im äußeren Bereich nicht zugegriffen werden.
In der Funktion bar () wird die lokale Variable nicht mit einer VAR -Anweisung deklariert, sondern definiert lokal als globale Variable direkt. Daher kann auf diese Variable durch den äußeren Bereich zugegriffen werden.
Die Codekopie lautet wie folgt:
local = {};
// Die Definition hier ist gleichbedeutend mit
global.local = {};
1.2 Geltungskette
In der JavaScript-Programmierung werden Sie definitiv auf mehrschichtige Funktionsszenarien stoßen, was eine typische Darstellung von Bereichsketten darstellt.
Wie im folgenden Code gezeigt:
Die Codekopie lautet wie folgt:
Funktion foo () {
var val = 'Hallo';
Funktions bar () {
Funktionsbaz () {
global.val = 'Welt;'
}
baz ();
console.log (val); // => Hallo
}
Bar();
}
foo ();
Basierend auf der vorherigen Erklärung zum Umfang denken Sie vielleicht, dass das im Code hier gezeigte Ergebnis Welt ist, aber das tatsächliche Ergebnis ist Hallo. Viele Anfänger werden sich hier verwirrt fühlen. Schauen wir uns also an, wie dieser Code funktioniert.
Seit JavaScript beginnt die Suche nach variablen Kennungen vom aktuellen Bereich und schaut nach außen bis zum globalen Bereich. Daher kann der Zugriff auf Variablen im JavaScript -Code nur nach außen, jedoch nicht umgekehrt, ausgeführt werden.
Die Ausführung der BAZ () -Funktion definiert eine globale VALIBLE im globalen Bereich. In der Funktion bar () ist bei dem Zugriff auf das Kennungsval das Prinzip der Suche von innen nach außen: Wenn es nicht im Bereich der Balkenfunktion gefunden wird, geht es zur vorherigen Schicht, dh dem Umfang der FOO () -Funktion.
Der Schlüssel zum Verwechseln aller ist jedoch: Dieser Zugriff auf den Kennung findet eine übereinstimmende Variable im Bereich der FOO () -Funktion und schaut nicht weiter nach außen, sodass die in der BAZ () -Funktion definierte globale VAL -Funktion in diesem variablen Zugriff keinen Einfluss hat.
1.3 Schließung
Wir wissen, dass die Identifikator-Suche in JavaScript dem Prinzip von inside nach außen folgt. Angesichts der Komplexität der Geschäftslogik ist jedoch eine einzige Lieferaufstellung den zunehmenden neuen Bedürfnissen nicht erfüllt.
Schauen wir uns den folgenden Code an:
Die Codekopie lautet wie folgt:
Funktion foo () {
var local = 'Hallo';
return function () {
Lokal zurückkehren;
};
}
var bar = foo ();
console.log (bar ()); // => Hallo
Die Technologie, die es dem äußeren Bereich ermöglicht, wie hier gezeigt auf inneren Bereich zuzugreifen, ist der Verschluss. Dank der Anwendung von Funktionen höherer Ordnung ist der Umfang der FOO () -Funktion "erweitert".
Die FOO () -Funktion gibt eine anonyme Funktion zurück, die im Rahmen der Funktion foo () existiert, sodass Sie auf die lokale Variable im Bereich der Funktion foo () zugreifen und ihre Referenz speichern können. Da diese Funktion die lokale Variable direkt zurückgibt, kann die Funktion bar () direkt im äußeren Bereich ausgeführt werden, um die lokale Variable zu erhalten.
Verschlüsse sind ein hochrangiges Merkmal von JavaScript, und wir können sie verwenden, um immer komplexere Effekte zu erzielen, um unterschiedliche Bedürfnisse zu erfüllen. Es ist jedoch zu beachten, dass die Variablen in diesem Bereich nicht zerstört werden, nachdem die Funktion ausgeführt wurde, bis alle Verweise auf die internen Variablen abgebrochen werden. Daher kann die Anwendung von Schließungen leicht dazu führen, dass der Speicher unfröhlich ist.
2. JavaScripts Speicherrecycling -Mechanismus
Hier werde ich die von Chrome und Node.js verwendete V8 -Engine nehmen und von Google als Beispiel gestartet, um den Speicherrecycling -Mechanismus von JavaScript kurz vorzustellen. Für detailliertere Inhalte können Sie das Buch meines guten Freundes Park Lings "eingehend und leicht zu verstehen node.js" für das Lernen erwerben, was eine sehr detaillierte Einführung in das Kapitel "Speicherkontrolle" ist.
In V8 werden alle JavaScript -Objekte über den "Haufen" zugewiesen.
Wenn wir Werte im Code deklarieren und zuweisen, wird V8 einen Teil dieser Variablen im Heap -Speicher bereitstellen. Wenn der angeforderte Speicher nicht ausreicht, um diese Variable zu speichern, gilt V8 weiterhin für den Speicher, bis die Haufengröße die Speichergrenze von V8 erreicht. Standardmäßig beträgt die Obergrenze des Heap-Speichers von V8 auf 64-Bit-Systemen und 732 MB auf 32-Bit-Systemen, was etwa 1,4 GB und 0,7 GB beträgt.
Darüber hinaus verwaltet V8 JavaScript -Objekte im Heap -Speicher in Generationen: die neue Generation und die alte Generation. Die neue Generation sind JavaScript -Objekte mit kurzen Überlebenszyklen wie temporären Variablen, Zeichenfolgen usw.; Während die alte Generation Objekte mit langen Überlebenszyklen nach mehreren Müllsammlungen sind, wie z. B. Hauptcontroller, Serverobjekte usw.
Müllrecyclingalgorithmen waren schon immer ein wichtiger Bestandteil der Entwicklung von Programmiersprachen, und die in V8 verwendeten Müllrecyclingalgorithmen sind hauptsächlich wie folgt:
1. SCAVANGE -Algorithmus: Der Speicherplatzmanagement wird durch Kopieren durchgeführt, hauptsächlich im Speicherraum der neuen Generation;
2.Mark-Sweep-Algorithmus und Mark-Kompaktalgorithmus: Das Heap-Speicher wird sortiert und durch Markierung recycelt, die hauptsächlich zur Inspektion und Recycling von Objekten der alten Generation verwendet werden.
PS: Detailliertere Implementierungen für die V8 -Müllsammlung können durch Lesen verwandter Bücher, Dokumente und Quellcode gelernt werden.
Schauen wir uns an, welche Umstände die JavaScript -Engine recyceln, welche Objekte.
2.1 Umfang und Referenz
Anfänger glauben oft fälschlicherweise, dass das in der Funktion deklarierte Objekt beim Ausführen der Funktion zerstört wird. Tatsächlich ist dieses Verständnis jedoch nicht streng und umfassend, und es ist leicht, verwirrt zu werden.
Referenz ist ein sehr wichtiger Mechanismus in der JavaScript -Programmierung, aber seltsamerweise werden die meisten Entwickler nicht darauf achten oder sie gar nicht verstehen. Referenz bezieht sich auf die abstrakte Beziehung "Codezugriff auf Objekte". Es ist etwas ähnlich wie C/C ++ - Zeiger, aber nicht dasselbe. Referenz ist auch der kritischste Mechanismus der JavaScript -Engine in der Müllsammlung.
Der folgende Code ist ein Beispiel:
Die Codekopie lautet wie folgt:
// ......
var val = 'Hallo Welt';
Funktion foo () {
return function () {
Rückkehr val;
};
}
global.bar = foo ();
// ......
Können Sie nach dem Lesen dieses Codes sagen, welche Objekte nach diesem Teil des Codes noch immer überleben?
Nach relevanten Prinzipien umfassen die Objekte, die in diesem Code nicht recycelt und freigegeben werden, Val und Bar (). Was genau lässt sie nicht recycelt werden?
Wie führt der JavaScript -Engine die Müllsammlung durch? Der oben erwähnte Garbage Collection -Algorithmus wird nur während des Recyclings verwendet. Woher weiß es also, welche Objekte recycelt werden können und welche Objekte weiterhin überleben müssen? Die Antwort ist ein Verweis auf ein JavaScript -Objekt.
In JavaScript -Code ist die JavaScript -Engine, selbst wenn Sie einfach einen Variablennamen als einzelne Zeile ohne etwas aufschreiben, der Meinung ist, dass dies ein Zugriffsverhalten für das Objekt ist und es einen Verweis auf das Objekt gibt. Um sicherzustellen, dass das Verhalten des Müllsammlung keinen Einfluss auf den Betrieb der Programmlogik hat, darf die JavaScript -Engine die verwendeten Objekte nicht recyceln, andernfalls ist es chaotisch. Daher ist der Standard für die Beurteilung, ob ein Objekt verwendet wird, ob immer noch ein Hinweis auf das Objekt vorliegt. Tatsächlich ist dies jedoch ein Kompromiss, da JavaScript -Referenzen übertragen werden können, sodass möglicherweise einige Referenzen auf den globalen Bereich gebracht werden. Tatsächlich ist es jedoch nicht mehr erforderlich, in der Geschäftslogik auf sie zugreifen zu können, und sollte recycelt werden, aber die JavaScript -Engine wird immer noch streng glauben, dass das Programm noch erforderlich ist.
Die Verwendung von Variablen und Referenzen in der richtigen Haltung ist der Schlüssel zur Optimierung von JavaScript auf der Sprachebene.
3. Optimieren Sie Ihr JavaScript
Endlich auf den Punkt gebracht. Vielen Dank, dass Sie dies mit Geduld gesehen haben. Nach so vielen obigen Einführungen glaube ich, dass Sie ein gutes Verständnis für den Mechanismus des Gedächtnismanagements von JavaScript haben. Dann werden Sie die folgenden Fähigkeiten besser fühlen.
3.1 Nutzen Sie die Funktionen gut
Wenn Sie die Angewohnheit haben, hervorragende JavaScript-Projekte zu lesen, werden Sie feststellen, dass bei der Entwicklung von JavaScript-Code Front-End oft viele große Leute eine anonyme Funktion verwenden, um ihn auf die äußerste Ebene des Codes zu wickeln.
Die Codekopie lautet wie folgt:
(function () {
// Hauptgeschäftskode
}) ();
Einige sind noch fortgeschrittener:
Die Codekopie lautet wie folgt:
; (Funktion (Win, doc, $, undefiniert) {
// Hauptgeschäftskode
}) (Fenster, Dokument, jQuery);
Sogar Front-End-Modularladerlösungen wie Anforderungen, SeaJs, Ozjs usw. alle nehmen eine ähnliche Form an:
Die Codekopie lautet wie folgt:
// fordernjs
Define (['JQuery'], Funktion ($) {
// Hauptgeschäftskode
});
// SeaJs
Define ('Modul', ['DEP', 'Undercore'], Funktion ($, _) {
// Hauptgeschäftskode
});
Wenn Sie sagen, dass viele Codes von Node.js Open Source -Projekten nicht auf diese Weise verarbeitet werden, dann liegen Sie falsch. Bevor Sie den Code tatsächlich ausführen, wickelt Node.js jede .js -Datei in das folgende Formular ein:
Die Codekopie lautet wie folgt:
(Funktion (exportiert, erfordern, modul, __dirname, __FileName) {
// Hauptgeschäftskode
});
Was sind die Vorteile davon? Wir alle wissen, dass wir zu Beginn des Artikels gesagt haben, dass JavaScript Funktionen mit Aussagen und globalem Umfang aufnehmen kann. Wir wissen auch, dass Objekte, die im globalen Bereich definiert sind, bis zum Ablauf des Prozesses überleben können. Wenn es sich um ein großes Objekt handelt, ist es problematisch. Zum Beispiel machen einige Leute gerne Vorlagen in JavaScript:
Die Codekopie lautet wie folgt:
<? Php
$ db = mysqli_connect (Server, Benutzer, Passwort, 'MyApp');
$ topics = mysqli_query ($ db, "aus themen ausgewählt;");
?>
<! docType html>
<html lang = "en">
<kopf>
<meta charset = "utf-8">
<titels> Bist du ein lustiger Typ, der von Affen eingeladen ist? </title>
</head>
<body>
<ul id = "themen"> </ul>
<script type = "text/tmpl" id = "topic-tmpl">
<li>
<H1> <%= title%> </h1>
<p> <%= Content%> </p>
</li>
</script>
<script type = "text/javaScript">
var data = <? php echo json_encode ($ themen); ?>;
var topictmpl = document.querySelector ('#topic-tmpl'). Innerhtml;
var render = Funktion (TMLP, Ansicht) {
var kompiliert = tmlp
.Replace (// n/g, '// n')
.Replace (/<%= ([/s/s]+?)%>/g, Funktion (Übereinstimmung, Code) {
return '" + Escape (' + code + ') +"';
});
kompiliert = [
'var res = "";',
'mit (Ansicht || {}) {',
'res = "' + kompiliert + '";',,
'}',
'return res;' '
] .Join ('/n');
var fn = neue Funktion ('Ansicht', kompiliert);
Rückgabe fn (Ansicht);
};
var topics = document.querySelector ('#themen');
Funktion init ()
Data.foreach (Funktion (Thema) {
topics.innerhtml += render (topictmpl, Thema);
});
}
init ();
</script>
</body>
</html>
Diese Art von Code ist oft in den Werken von Anfängern zu sehen. Was sind die Probleme hier? Wenn die aus der Datenbank erhaltene Datenmenge sehr groß ist, ist die Datenvariable im Leerlauf, nachdem das Front-End die Vorlagerenderung abgeschlossen hat. Da diese Variable jedoch im globalen Bereich definiert ist, recyceln und zerstören die JavaScript -Engine nicht. Dies wird weiterhin im Heap -Speicher der alten Generation existieren, bis die Seite geschlossen ist.
Wenn wir jedoch einige sehr einfache Modifikationen vornehmen und eine Funktionsebene außerhalb des logischen Codes einwickeln, ist der Effekt sehr unterschiedlich. Nach Abschluss der Benutzeroberfläche wird auch der Verweis des Code auf Daten storniert. Wenn die äußerste Funktion ausgeführt wird, beginnt die JavaScript -Engine, die darin enthaltenen Objekte zu überprüfen, und Daten können recycelt werden.
3.2 Definieren Sie niemals globale Variablen
Wir haben gerade darüber gesprochen, wenn eine Variable im globalen Bereich definiert wird, wird die JavaScript -Engine sie standardmäßig nicht recyceln und zerstören. Dies wird weiterhin im Heap -Speicher der alten Generation existieren, bis die Seite geschlossen ist.
Dann haben wir immer ein Prinzip verfolgt: Verwenden Sie niemals globale Variablen. Obwohl globale Variablen in der Tat sehr einfach zu entwickeln sind, sind die Probleme, die durch globale Variablen verursacht werden, weitaus schwerwiegender als die Komfort, die sie mit sich bringt.
Variablen weniger wahrscheinlich recycelt werden;
1. Verwirrung ist leicht zu verursachen, wenn mehrere Personen zusammenarbeiten;
2. Es ist leicht, in die Bereichskette einzusteigen.
3. In Verbindung mit der obigen Wickelfunktion können wir auch "globale Variablen" durch Verpackungsfunktionen verarbeiten.
3.3 Manuell Unreferenzvariablen
Wenn eine Variable in der Geschäftsordnung nicht benötigt wird, kann die Variable manuell Derferenziert werden, um sie recycelt zu machen.
Die Codekopie lautet wie folgt:
var data = { / * einige Big Data * /};
// bla bla bla
Daten = NULL;
3.4 Nutzen Sie die Rückrufe gut aus
Zusätzlich zur Verwendung von Schließungen für den internen variablen Zugriff können wir auch die jetzt sehr beliebte Rückruffunktion für die Geschäftsverarbeitung verwenden.
Die Codekopie lautet wie folgt:
Funktion getData (Callback) {
var data = 'einige Big Data';
Rückruf (NULL, Daten);
}
getData (Funktion (err, data) {
console.log (Daten);
Rückruffunktionen sind eine Technologie des CPS (Continuation Passing Style). Dieser Programm der Programmierung überträgt den geschäftlichen Fokus der Funktion vom Rückzugswert auf die Rückruffunktion. Und es hat viele Vorteile gegenüber Schließungen:
1. Wenn die übergebenen Parameter der Grundtyp sind (z. B. Zeichenfolgen, numerische Werte), werden die formalen Parameter, die in der Rückruffunktion übergeben wurden, kopierte Werte und es ist einfacher, nach Verwendung der Geschäftsordnung recycelt zu werden.
2. Durch Rückrufe, zusätzlich zu den synchronen Anfragen, können wir sie auch in der asynchronen Programmierung verwenden, die jetzt ein sehr beliebter Schreibstil ist.
3. Die Rückruffunktion selbst ist normalerweise eine temporäre anonyme Funktion. Sobald die Anforderungsfunktion ausgeführt wurde, wird die Referenz auf die Rückruffunktion selbst storniert und recycelt.
3.5 gutes Verschlussmanagement
Wenn unser Geschäftsbedarf (wie z. B. bindende Ereignisse, private Attribute, Rückrufe mit Argumenten usw.) Schließungen verwenden müssen, sollten Sie sich bitte vorsichtig mit den Details befassen.
Schleifenbindungsereignisse können als obligatorischer Kurs für den Einstieg mit JavaScript -Schließungen sein. Nehmen wir ein Szenario an: Es gibt sechs Tasten, die sechs Ereignissen entsprechen. Wenn der Benutzer auf die Schaltfläche klickt, werden die entsprechenden Ereignisse an der angegebenen Stelle ausgegeben.
Die Codekopie lautet wie folgt:
var btns = document.querySelectorAll ('. btn'); // 6 Elemente
var output = document.querySelector ('#output');
var Ereignisse = [1, 2, 3, 4, 5, 6];
// Fall 1
für (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = function (evt) {
output.innertext + = 'geklickt' + Ereignisse [i];
};
}
// Fall 2
für (var i = 0; i <btns.length; i ++) {
BTNS [i] .onclick = (Funktion (Index) {
Rückgabefunktion (evt) {
output.innertext + = 'klickte' + Ereignisse [Index];
};
})(ich);
}
// Fall 3
für (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = (function (event) {
Rückgabefunktion (evt) {
output.inNertext + = 'geklickt' + Ereignis;
};
}) (Ereignisse [i]);
}
Die erste Lösung hier ist offensichtlich ein typischer Ereignisfehler. Ich werde es hier nicht ausführlich erklären. Sie können sich im Detail auf meine Antwort auf einen Internetnutzer beziehen. Der Unterschied zwischen der zweiten und dritten Lösungen liegt in den im Verschluss übergebenen Parametern.
Die im zweiten Schema übergebenen Parameter sind der aktuelle Schleifen -Index, während letzteres direkt in das entsprechende Ereignisobjekt übergeben wird. Tatsächlich ist letzteres für große Mengen an Datenanwendungen besser geeignet, da in der JavaScript -Funktionsprogrammierung die in Funktionsaufrufen übergebenen Parameter grundlegende Typen Objekte sind. Daher sind die formalen Parameter, die im Funktionsgremium erhalten wurden, ein Kopienwert, so dass dieser Wert als lokale Variable innerhalb des Funktionsbereichs des Funktionsbörse definiert wird. Nach Abschluss der Ereignisbindung kann die Ereignisvariable manuell Derferenziert werden, um die Speicherverwendung im äußeren Bereich zu verringern. Wenn ein Element gelöscht wird, werden auch die entsprechende Funktionsereignisfunktion, das Ereignisobjekt und die Schließfunktion zerstört und recycelt.
3.6 Speicher ist kein Cache
Die Rolle des Caching in der Geschäftsentwicklung spielt eine wichtige Rolle und kann die Belastung durch Raumzeitressourcen verringern. Es ist jedoch zu beachten, dass Sie den Speicher nicht so einfach als Cache verwenden sollten. Die Erinnerung ist für jede Programmentwicklung an jedem Zentimeter Land. Wenn es sich nicht um eine sehr wichtige Ressource handelt, stellen Sie sie bitte nicht direkt in den Speicher oder formulieren Sie einen Ablaufmechanismus, um den Ablauf -Cache automatisch zu zerstören.
4. Überprüfen Sie die Speicherverwendung von JavaScript
In der täglichen Entwicklung können wir auch einige Tools verwenden, um die Speicherverwendung in JavaScript zu analysieren und zu beheben.
4.1 Blink/Webkit Browser
Im Blink/Webkit -Browser (Chrome, Safari, Opera usw.) können wir das Profile -Tool von Entwickler -Tools verwenden, um Speicherprüfungen für unsere Programme durchzuführen.
4.2 Speicherprüfung in node.js
In Node.js können wir Node-Heapdump- und Knoten-Memwatch-Module für die Speicherprüfung verwenden.
Die Codekopie lautet wie folgt:
var heapdump = required ('heapdump');
var fs = fordert ('fs');
var path = require ('path');
fs.writeFilesync (path.join (__ DirName, 'app.pid'), process.pid);
// ...
Die Codekopie lautet wie folgt: <span style = "Schriftfamilie: Georgia," Times New Roman "," Bitstream Charter ", Times, Serif; Schriftgröße: 14px; Zeilenhöhe: 1,5EM;"> Nach dem Einführen eines Knoten-Hapdump-Vorgangs müssen wir einen Signal-Hap-Hap-Signal in einen Node-Hapdump-Prozess einsetzen. der Haufen Speicher. </span>
Kopieren Sie den Code wie folgt: $ Kill -USR2 (Cat App.pid)
Auf diese Weise gibt es eine Snapshot-Datei, die im Format Heapdump- <Sec>. Wir können es mit dem Profile -Tool in den Entwickler -Tools des Browsers öffnen und es überprüfen.
5. Zusammenfassung
Der Artikel kommt bald wieder. Diese Freigabe zeigt Ihnen hauptsächlich den folgenden Inhalt:
1. JavaScript ist eng mit der Speicherverwendung auf Sprachebene verbunden.
2. Speichermanagement- und Recycling -Mechanismen in JavaScript;
3.. Wie man den Speicher effizienter verwendet, damit das produzierte JavaScripter erweiterter und energetischer ist;
4. Durchführen von Speicherprüfungen bei Begriffsgespeicherproblemen.
Ich hoffe, dass Sie durch das Erlernen dieses Artikels einen besseren JavaScript -Code produzieren können, damit sich Ihre Mutter wohl fühlt und sich Ihr Chef wohl fühlt.