Im Vergleich zu C/C konnten wir durch die von uns verwendete Speicherverarbeitung von JavaScript dem Schreiben der Geschäftslogik während der Entwicklung mehr Aufmerksamkeit schenken. Angesichts der kontinuierlichen Komplexität des Geschäfts und der Entwicklung von Single-Page-Anwendungen, mobilen HTML5-Anwendungen, Node.js-Programmen usw. sind jedoch Phänomene wie Verzögerungen und Speicherüberlauf, die durch Speicherprobleme in JavaScript verursacht werden, keine Unbekannten mehr.
In diesem Artikel werden die Speichernutzung und -optimierung auf der Sprachebene von JavaScript erläutert. Von den Aspekten, mit denen jeder vertraut ist oder von denen er ein wenig gehört hat, bis hin zu den Bereichen, die den meisten Menschen nicht auffallen, werden wir sie einzeln analysieren.
1. Speicherverwaltung auf Sprachebene
1.1 Geltungsbereich
Der Bereich ist ein sehr wichtiger Betriebsmechanismus in der JavaScript-Programmierung. Bei der synchronen JavaScript-Programmierung erregt er nicht die volle Aufmerksamkeit von Anfängern, aber bei der asynchronen Programmierung sind gute Fähigkeiten zur Bereichskontrolle der Schlüssel für die JavaScript-Entwicklung. Darüber hinaus spielt der Bereich eine entscheidende Rolle bei der JavaScript-Speicherverwaltung.
In JavaScript können die Bereiche durch Funktionsaufrufe, mit Anweisungen und globalem Bereich gebildet werden.
Nehmen Sie den folgenden Code als Beispiel:
Hier definieren wir die Funktionen foo() und bar(). Ihre Absicht ist es, eine Variable mit dem Namen local zu definieren. Aber das Endergebnis war völlig anders.
In der Funktion foo() verwenden wir die var-Anweisung, um eine lokale Variable zu deklarieren und zu definieren. Da innerhalb des Funktionskörpers ein Bereich gebildet wird, wird diese Variable im Bereich definiert. Darüber hinaus führt der Hauptteil der Funktion foo() keine Verarbeitung zur Bereichserweiterung durch, sodass nach der Ausführung der Funktion auch die lokale Variable zerstört wird. Auf die Variable kann im äußeren Bereich nicht zugegriffen werden.
In der Funktion bar() wird die lokale Variable nicht mit der var-Anweisung deklariert, sondern local wird direkt als globale Variable definiert. Daher kann der äußere Bereich auf diese Variable zugreifen.
Wie im folgenden Code gezeigt:
Der entscheidende Punkt, der alle verwirrt, ist jedoch hier: Dieser Identifikatorzugriff findet eine passende Variable im Bereich der foo()-Funktion und sucht nicht weiter nach außen, also in der baz()-Funktion Das definierte Global Der Variablenwert hat keinen Einfluss auf diesen Variablenzugriff.
1.3 Schließung
Wir wissen, dass die Suche nach Bezeichnern in JavaScript dem Inside-Out-Prinzip folgt. Angesichts der Komplexität der Geschäftslogik reicht eine einzelne Liefersequenz jedoch bei weitem nicht aus, um den zunehmenden neuen Anforderungen gerecht zu werden.
Schauen wir uns zunächst den folgenden Code an:
Kopieren Sie den CodeDie hier gezeigte Technologie, die es dem äußeren Zielfernrohr ermöglicht, auf das innere Zielfernrohr zuzugreifen, ist der Verschluss (Closure). Durch den Einsatz von Funktionen höherer Ordnung wurde der Umfang der Funktion foo() „erweitert“.
Die Funktion foo() gibt eine anonyme Funktion zurück, die im Bereich der Funktion foo() existiert, sodass Sie auf die lokale Variable im Bereich der Funktion foo() zugreifen und deren 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 abzurufen.
Closure ist eine erweiterte Funktion von JavaScript. Wir können damit komplexere Effekte erzielen, um unterschiedliche Anforderungen zu erfüllen. Es ist jedoch zu beachten, dass die Variablen im Gültigkeitsbereich nach der Ausführung der Funktion nicht unbedingt zerstört werden, bis alle Verweise auf die internen Variablen freigegeben werden, da die Funktion mit internen Variablenreferenzen aus der Funktion entfernt wird. Daher kann die Anwendung von Verschlüssen leicht dazu führen, dass der Speicher nicht freigegeben werden kann.
2. JavaScript-Speicherrecyclingmechanismus
Hier werde ich die von Google eingeführte V8-Engine, die von Chrome und Node.js verwendet wird, als Beispiel nehmen, um den Speicherrecyclingmechanismus von JavaScript kurz vorzustellen. Für detailliertere Inhalte können Sie das Buch „Speaking in“ meines guten Freundes Pu Ling kaufen Einfache und tiefe Sprache „Node.js“ zum Erlernen, in der das Kapitel „Speichersteuerung“ eine recht ausführliche Einführung enthält.
In V8 wird allen JavaScript-Objekten Speicher über den „Heap“ zugewiesen.
Wenn wir eine Variable im Code deklarieren und einen Wert zuweisen, weist V8 der Variablen einen Teil des Heap-Speichers zu. Wenn der zugewiesene Speicher nicht ausreicht, um diese Variable zu speichern, beantragt V8 weiterhin Speicher, bis die Heap-Größe die obere Speichergrenze von V8 erreicht. Standardmäßig beträgt die Obergrenze der Heap-Speichergröße von V8 1464 MB in 64-Bit-Systemen und 732 MB in 32-Bit-Systemen, was etwa 1,4 GB und 0,7 GB entspricht.
Darüber hinaus verwaltet V8 JavaScript-Objekte im Heap-Speicher nach Generation: neue Generation und alte Generation. Die neue Generation bezieht sich auf JavaScript-Objekte mit einem kurzen Lebenszyklus, z. B. temporäre Variablen, Zeichenfolgen usw., während sich die alte Generation auf Objekte bezieht, die mehrere Garbage Collections überstanden haben und einen langen Lebenszyklus haben, z. B. Hauptcontroller und Serverobjekte , usw.
Garbage-Collection-Algorithmen waren schon immer ein wichtiger Teil der Entwicklung von Programmiersprachen, und die in V8 verwendeten Garbage-Collection-Algorithmen umfassen hauptsächlich Folgendes:
1.Scavange-Algorithmus: Speicherplatzverwaltung durch Kopieren, hauptsächlich für den Speicherplatz der neuen Generation verwendet
2.Mark-Sweep-Algorithmus und Mark-Compact-Algorithmus: Organisieren und Organisieren des Heap-Speichers durch Markieren Recycling ist Wird hauptsächlich zur Inspektion und zum Recycling von Gegenständen der alten Generation verwendet.
PS: Eine detailliertere V8-Garbage-Collection-Implementierung kann durch das Lesen relevanter Bücher, Dokumente und Quellcodes erlernt werden.
Sehen wir uns an, welche Objekte die JavaScript-Engine unter welchen Umständen recycelt.
2.1 Geltungsbereich und Referenz
Anfänger glauben oft fälschlicherweise, dass nach Abschluss der Ausführung der Funktion das in der Funktion deklarierte Objekt zerstört wird. Tatsächlich ist dieses Verständnis jedoch nicht streng und umfassend und kann leicht zu Verwirrung führen.
Referenz ist ein sehr wichtiger Mechanismus in der JavaScript-Programmierung, aber das Seltsame ist, dass die meisten Entwickler ihm keine Beachtung schenken oder ihn nicht einmal verstehen. Referenz bezieht sich auf die abstrakte Beziehung des „Codezugriffs auf Objekte“. Sie ähnelt in gewisser Weise C/C-Zeigern, ist aber nicht dasselbe. Referenzen sind auch der wichtigste Mechanismus für die Speicherbereinigung durch die JavaScript-Engine.
Nehmen Sie den folgenden Code als Beispiel:
Können Sie nach dem Lesen dieses Codes erkennen, welche Objekte nach der Ausführung dieses Teils des Codes noch am Leben sind?
Nach den einschlägigen Grundsätzen umfassen die Objekte, die in diesem Code nicht recycelt und freigegeben wurden, val und bar(). Was ist der Grund, warum sie nicht recycelt werden können?
Wie führt die JavaScript-Engine die Speicherbereinigung durch? Der zuvor erwähnte Garbage-Collection-Algorithmus wird nur beim Recycling verwendet. Woher weiß er also, welche Objekte recycelt werden können und welche Objekte weiterhin überleben müssen? Die Antwort ist ein Verweis auf ein JavaScript-Objekt.
Selbst wenn Sie im JavaScript-Code einfach einen Variablennamen als separate Zeile schreiben, ohne einen Vorgang auszuführen, geht die JavaScript-Engine davon aus, dass es sich um ein Zugriffsverhalten auf das Objekt handelt und es einen Verweis auf das Objekt gibt. Um sicherzustellen, dass das Verhalten der Garbage Collection den Betrieb der Programmlogik nicht beeinträchtigt, darf die JavaScript-Engine die verwendeten Objekte nicht recyceln, da es sonst zu Chaos kommt. Das Kriterium für die Beurteilung, ob ein Objekt verwendet wird, ist also, ob noch ein Verweis auf das Objekt vorhanden ist. Tatsächlich ist dies jedoch ein Kompromiss, da JavaScript-Referenzen übertragen werden können. Einige Referenzen werden dann möglicherweise in den globalen Bereich übertragen, es besteht jedoch keine Notwendigkeit, sie in der Geschäftslogik zu ändern. Sobald darauf zugegriffen wird, sollten sie recycelt werden. aber die JavaScript-Engine wird immer noch fest davon überzeugt sein, dass das Programm es noch benötigt.
Die richtige Verwendung von Variablen und Referenzen ist der Schlüssel zur Optimierung von JavaScript auf Sprachebene.
3. Optimieren Sie Ihr JavaScript
Kommen wir endlich zum Punkt. Vielen Dank für Ihre Geduld beim Lesen. Ich glaube, dass Sie den Speicherverwaltungsmechanismus von JavaScript bereits gut verstanden haben .
3.1 Funktionen sinnvoll nutzen
Wenn Sie die Angewohnheit haben, hervorragende JavaScript-Projekte zu lesen, werden Sie feststellen, dass viele Experten bei der Entwicklung von Front-End-JavaScript-Code häufig eine anonyme Funktion verwenden, um die äußerste Ebene des Codes zu umschließen.
Was sind die Vorteile davon? Wir alle wissen, dass die Bereiche in JavaScript, wie am Anfang des Artikels erwähnt, Funktionsaufrufe mit Anweisungen und einen globalen Bereich umfassen. Und wir wissen auch, dass im globalen Bereich definierte Objekte wahrscheinlich bis zum Beenden des Prozesses überleben. Wenn es sich um ein großes Objekt handelt, ist dies problematisch. Einige Leute führen beispielsweise gerne das Rendern von Vorlagen in JavaScript durch:
这种代码在新手的作品中经常能看得到,这里存在什么问题呢?如果在从数据库中获取到的数据的量是非常大的话, 前端完成模板渲染以后, data变量便被闲置在一边.可因为这个变量是被定义在全局作用域中的, 所以JavaScript引擎不会将其回收销毁.如此该变量就会一直存在于老生代堆内存中,直到页面被关闭.
可是如果我们作出一些很简单的修改,在逻辑代码外包装一层函数,这样效果就大不同了.当UI渲染完成之后,代码对data的引用也就随之解除,而在最外层函数执行完毕时, JavaScript引擎就开始对其中的对象进行检查, data也就可以随之被回收.
3.2作用域中, 默认情况下JavaScript 引擎就不会将其回收销毁。直到页面被关闭.
那么我们就一直遵循一个原则:绝对不要使用全局变量.虽然全局变量在开发中确实很省事,但是全局变量所导致的问题远比其所带来的方便更严重.
使变量不易被回收;1.多人协作时容易产生混淆;
3.配合上面的包装函数,我们也可以通过包装函数来处理『全局变量』.
3.3了,那么就可以手工解除变量引用,以使其被回收.
代码如下:
var data = { /* some big data */ } ;
Die Callback-Funktion ist eine Continuation Passing Style (CPS)-Technologie. Dieser Programmierstil verlagert den Geschäftsschwerpunkt der Funktion vom Rückgabewert auf die Callback-Funktion. Und es hat viele Vorteile gegenüber Verschlüssen:
1. Wenn es sich bei den übergebenen Parametern um Grundtypen (z. B. Zeichenfolgen, Werte) handelt, handelt es sich bei den in der Rückruffunktion übergebenen formalen Parametern um kopierte Werte, und der Geschäftscode kann nach der Verwendung einfacher recycelt werden.
2 .Durch Rückrufe können wir es zusätzlich zum Abschließen synchroner Anforderungen auch in der asynchronen Programmierung verwenden, was derzeit ein sehr beliebter Schreibstil ist.
3 Die Rückruffunktion selbst ist normalerweise eine temporäre anonyme Funktion ausgeführt wird, wird die Referenz der Callback-Funktion selbst freigegeben und selbst recycelt.
3.5 Gutes Verschlussmanagement
Wenn unsere Geschäftsanforderungen (z. B. Schleifenereignisbindung, private Eigenschaften, Rückrufe mit Parametern usw.) Abschlüsse verwenden müssen, achten Sie bitte auf die Details.
Schleifenbindungsereignisse können als erforderlicher Kurs für den Einstieg in JavaScript-Abschlüsse bezeichnet werden. Wir gehen von einem Szenario aus: Es gibt sechs Schaltflächen, die sechs Ereignistypen entsprechen. Wenn der Benutzer auf die Schaltfläche klickt, wird das entsprechende Ereignis ausgegeben am angegebenen Ort.
Die erste Lösung hier ist offensichtlich ein typischer Loop-Binding-Ereignisfehler. Ich werde hier nicht auf Details eingehen, Sie können sich auf die Antwort beziehen, die ich einem Internetnutzer gegeben habe, und der Unterschied zwischen der zweiten und dritten Lösung in den eingehenden Parametern.
Der in der zweiten Lösung übergebene Parameter ist der aktuelle Schleifenindex, während letzterer direkt das entsprechende Ereignisobjekt übergibt. Letzteres eignet sich tatsächlich besser für umfangreiche Datenanwendungen, da in der funktionalen JavaScript-Programmierung die beim Aufruf der Funktion übergebenen Parameter grundlegende Typobjekte sind, sodass die im Funktionskörper erhaltenen formalen Parameter kopierte Werte sind. Daher wird dieser Wert als lokale Variable im Bereich des Funktionskörpers definiert. Nach Abschluss der Ereignisbindung kann die Ereignisvariable manuell dereferenziert werden, um die Speichernutzung im äußeren Bereich zu reduzieren. Und wenn ein Element gelöscht wird, werden auch die entsprechende Ereignisüberwachungsfunktion, das Ereignisobjekt und die Abschlussfunktion zerstört und recycelt.
3.6 Speicher ist kein Cache
Caching spielt eine wichtige Rolle in der Geschäftsentwicklung und kann die Belastung von Zeit- und Platzressourcen reduzieren. Es ist jedoch zu beachten, dass der Speicher nicht einfach als Cache verwendet werden kann. Speicher ist eine wertvolle Ressource für jede Programmentwicklung. Wenn es sich nicht um eine sehr wichtige Ressource handelt, platzieren Sie ihn bitte nicht direkt im Speicher und entwickeln Sie keinen Ablaufmechanismus, um den abgelaufenen Cache automatisch zu zerstören.
4. Überprüfen Sie die JavaScript-Speichernutzung
In der täglichen Entwicklung können wir auch einige Tools verwenden, um die Speichernutzung in JavaScript zu analysieren und Fehler zu beheben.
4.1 Blink-/Webkit-Browser
In Blink-/Webkit-Browsern (Chrome, Safari, Opera usw.) können wir das Profiles-Tool der Developer Tools verwenden, um den Speicher unseres Programms zu überprüfen.
4.2 Speicherüberprüfung in Node.js
In Node.js können wir die Module node-heapdump und node-memwatch zur Speicherüberprüfung verwenden.
De cette façon, il y aura un fichier instantané nommé au format heapdump-
5.Résumé
La fin de l'article est bientôt arrivée. Ce partage vous montre principalement les points suivants :
1. JavaScript est étroitement lié à l'utilisation de la mémoire au niveau du langage ;
2. Mécanisme de gestion et de recyclage de la mémoire en JavaScript
3 Comment utiliser la mémoire plus efficacement pour que le JavaScript produit puisse avoir plus de vitalité ; pour l'expansion ;
4. Comment effectuer une vérification de la mémoire en cas de problèmes de mémoire.
J'espère qu'en étudiant cet article, vous pourrez produire un meilleur code JavaScript, qui rassurera votre mère et votre patron.