In dem Material, dessen erster Teil der Übersetzung wir heute veröffentlichen, wird erläutert, wie die V8-JavaScript-Engine die besten Möglichkeiten zur Darstellung verschiedener JS-Werte im Speicher auswählt und wie sich dies auf die internen Mechanismen von V8 beim Arbeiten mit sogenannten 
Formularen auswirkt 
Objekte (Form). All dies wird uns helfen, das Wesentliche des jüngsten 
Problems der 
React-Leistung zu klären.

JavaScript-Datentypen
Jeder JavaScript-Wert kann nur einen der acht vorhandenen Datentypen haben: 
Number , 
String , 
Symbol , 
BigInt , 
Boolean , 
Undefined , 
Null und 
Object .
JavaScript-DatentypenDie Art des Wertes kann mit dem Operator 
typeof werden, es gibt jedoch eine wichtige Ausnahme:
 typeof 42;  
Wie Sie sehen können, gibt der Befehl 
typeof null 'object' und nicht 
'null' , obwohl 
null einen eigenen Typ hat - 
Null . Um den Grund für diese Art von Verhalten zu verstehen, berücksichtigen wir die Tatsache, dass die Menge aller JavaScript-Typen in zwei Gruppen unterteilt werden kann:
- Objekte (d. H. Typ Object).
- Primitive Werte (dh alle nicht objektiven Werte).
In Anbetracht dieses Wissens stellt sich heraus, dass 
null „kein Objektwert“ bedeutet, während 
undefined „kein Wert“ bedeutet.
Primitive Werte, Objekte, null und undefiniertIn Anlehnung an diese Überlegungen im Geiste von Java hat Brendan Eich JavaScript so entworfen, dass der Operator 
typeof 'object' für die Werte der Typen zurückgibt, die sich in der vorherigen Abbildung rechts befinden. Alle Objektwerte und 
null kommen hierher. Aus diesem Grund ist der Ausdruck 
typeof null === 'object' wahr, obwohl die Sprachspezifikation einen separaten Typ 
Null .
Der Ausdruckstyp von v === 'Objekt' ist wahrDarstellung von Werten
JavaScript-Engines sollten in der Lage sein, alle JavaScript-Werte im Speicher darzustellen. Es ist jedoch wichtig zu beachten, dass Werttypen in JavaScript von der Darstellung durch JS-Engines im Speicher getrennt sind.
Beispielsweise ist ein Wert von 42 in JavaScript vom Typ 
number .
 typeof 42;  
Es gibt verschiedene Möglichkeiten, Ganzzahlen wie 42 im Speicher darzustellen:
Nach dem ECMAScript-Standard sind Zahlen 64-Bit-Gleitkommawerte, sogenannte Gleitkommazahlen mit doppelter Genauigkeit (Float64). Dies bedeutet jedoch nicht, dass JavaScript-Engines Zahlen immer in einer Float64-Ansicht speichern. Das wäre sehr, sehr ineffizient! Engines können andere interne Darstellungen von Zahlen verwenden - sofern das Verhalten der Werte genau mit dem Verhalten der Float64-Zahlen übereinstimmt.
Wie sich herausstellte, sind die meisten Zahlen in echten JS-Anwendungen gültige ECMAScript-Array- 
Indizes . Das heißt - ganze Zahlen im Bereich von 0 bis 2 
32 -2.
 array[0];  
JavaScript-Engines können das optimale Format für die Darstellung solcher Werte im Speicher auswählen. Dies geschieht, um den Code für Array-Elemente mithilfe von Indizes zu optimieren. Ein Prozessor, der Speicherzugriffsoperationen ausführt, benötigt die Array-Indizes als Zahlen, die in einer Ansicht mit einer 
Addition von zwei gespeichert sind. Wenn wir stattdessen die Indizes von Arrays in Form von Float64-Werten darstellen, würde dies eine Verschwendung von Systemressourcen bedeuten, da die Engine dann Float64-Zahlen in ein Format mit zwei Additionen konvertieren müsste und umgekehrt, wenn jemand auf ein Array-Element zugreift.
Die Darstellung von 32-Bit-Zahlen mit der Hinzufügung von bis zu zwei ist nicht nur zur Optimierung der Arbeit mit Arrays nützlich. Im Allgemeinen kann festgestellt werden, dass der Prozessor Ganzzahloperationen viel schneller ausführt als Operationen, die Gleitkommawerte verwenden. Deshalb ist im folgenden Beispiel der erste Zyklus ohne Probleme doppelt so schnell wie der zweite Zyklus.
 for (let i = 0; i < 1000; ++i) {   
Gleiches gilt für Berechnungen mit mathematischen Operatoren.
Beispielsweise hängt die Leistung des Operators, den Rest der Division vom nächsten Codefragment zu übernehmen, davon ab, welche Zahlen in die Berechnungen einbezogen werden.
 const remainder = value % divisor;  
Wenn beide Operanden durch ganze Zahlen dargestellt werden, kann der Prozessor das Ergebnis sehr effizient berechnen. In V8 gibt es eine zusätzliche Optimierung für Fälle, in denen der 
divisor Operand durch eine Zahl mit einer Zweierpotenz dargestellt wird. Für Werte, die als Gleitkommazahlen dargestellt werden, sind die Berechnungen viel komplizierter und dauern viel länger.
Da Ganzzahloperationen normalerweise viel schneller ausgeführt werden als Operationen mit Gleitkommawerten, scheint es, dass Engines einfach immer alle Ganzzahlen und alle Ergebnisse von Ganzzahloperationen in einem Format mit einer Addition von zwei speichern können. Leider würde ein solcher Ansatz die ECMAScript-Spezifikation verletzen. Wie bereits erwähnt, sieht der Standard die Darstellung von Zahlen im Float64-Format vor, und einige Operationen mit ganzen Zahlen können dazu führen, dass Ergebnisse in Form von Gleitkommazahlen angezeigt werden. Es ist wichtig, dass JS-Engines in solchen Situationen korrekte Ergebnisse liefern.
 
Obwohl im vorherigen Beispiel alle Zahlen auf der linken Seite der Ausdrücke Ganzzahlen sind, sind alle Zahlen auf der rechten Seite der Ausdrücke Gleitkommawerte. Aus diesem Grund kann keine der vorherigen Operationen in einem 32-Bit-Format mit einer Addition von bis zu zwei korrekt ausgeführt werden. JavaScript-Engines müssen besonders darauf achten, dass Sie bei der Ausführung von Ganzzahloperationen die richtigen Float64-Ergebnisse erhalten (obwohl sie ungewöhnlich aussehen können - wie im vorherigen Beispiel).
Bei kleinen Ganzzahlen, die in den Bereich der 31-Bit-Darstellung vorzeichenbehafteter Ganzzahlen fallen, verwendet V8 eine spezielle Darstellung namens 
Smi . Alles, was kein 
Smi Wert ist, wird als 
HeapObject Wert dargestellt. 
HeapObject ist die Adresse einer Entität im Speicher. Für Zahlen, die nicht in den 
Smi Bereich fallen, haben wir eine spezielle Art von 
HeapObject - die sogenannte 
HeapNumber .
 -Infinity  
Wie Sie im vorherigen Beispiel sehen können, werden einige JS-Nummern als 
Smi und andere als 
HeapNumber . Die V8-Engine ist hinsichtlich der Verarbeitung von 
Smi Nummern optimiert. Tatsache ist, dass kleine Ganzzahlen in echten JS-Programmen sehr häufig sind. Bei der Arbeit mit 
Smi Werten ist es nicht erforderlich, Speicher für einzelne Entitäten zuzuweisen. Ihre Verwendung ermöglicht es Ihnen außerdem, schnelle Operationen mit ganzen Zahlen durchzuführen.
Vergleich von Smi, HeapNumber und MutableHeapNumber
Lassen Sie uns darüber sprechen, wie die interne Struktur dieser Mechanismen aussieht. Angenommen, wir haben das folgende Objekt:
 const o = {  x: 42,  
Der Wert 42 der Eigenschaft des Objekts 
x wird als 
Smi codiert. Dies bedeutet, dass es im Objekt selbst gespeichert werden kann. Um den Wert 4.2 zu speichern, müssen Sie dagegen eine separate Entität erstellen. Im Objekt befindet sich eine Verknüpfung zu dieser Entität.
Speicherung verschiedener WerteAngenommen, wir führen den folgenden JavaScript-Code aus:
 ox += 10;  
In diesem Fall kann der Wert der Eigenschaft 
x an ihrem Speicherort aktualisiert werden. Tatsache ist, dass der neue Wert von 
x 52 ist und diese Zahl in den Bereich von 
Smi fällt.
Der neue Wert der Eigenschaft x wird dort gespeichert, wo der vorherige Wert gespeichert wurde.Der neue Wert von 
y , 5.2, passt jedoch nicht in den Bereich von 
Smi und unterscheidet sich außerdem vom vorherigen Wert von y - 4.2. Infolgedessen muss V8 Speicher für die neue 
HeapNumber Entität 
HeapNumber und bereits vom Objekt aus darauf verweisen.
Neue Entität HeapNumber zum Speichern des neuen y-WertsHeapNumber Entitäten sind unveränderlich. Auf diese Weise können Sie einige Optimierungen implementieren. Angenommen, wir möchten die Eigenschaft des Objekts 
x Wert der Eigenschaft 
y :
 ox = oy;  
Wenn Sie diesen Vorgang ausführen, können wir einfach auf dieselbe 
HeapNumber Entität verweisen und keinen zusätzlichen Speicher zuweisen, um denselben Wert zu speichern.
Einer der Nachteile der Immunität von HeapNuber-Entitäten besteht darin, dass die häufige Aktualisierung von Feldern mit Werten außerhalb des 
Smi Bereichs langsam ist. Dies wird im folgenden Beispiel demonstriert:
 
Bei der Verarbeitung der ersten Zeile wird eine Instanz von 
HeapNumber erstellt, deren Anfangswert 0,1 beträgt. Im Hauptteil des Zyklus ändert sich dieser Wert zu 1.1, 2.1, 3.1, 4.1 und schließlich zu 5.1. Infolgedessen werden beim Ausführen dieses Codes 6 Instanzen von 
HeapNumber , von denen fünf nach Abschluss der Schleife 
HeapNumber unterzogen werden.
HeapNumber-EntitätenUm dieses Problem zu vermeiden, verfügt V8 über eine Optimierung, bei der numerische Felder aktualisiert werden, deren Werte nicht an denselben Stellen in den 
Smi Bereich passen, an denen sie bereits gespeichert sind. Wenn in einem numerischen Feld Werte 
Smi werden, für die die 
Smi Entität nicht zum Speichern geeignet ist, markiert V8 dieses Feld in Form eines Objekts als 
Double und 
MutableHeapNumber der 
MutableHeapNumber Entität Speicher zu, in der der im Float64-Format dargestellte reale Wert gespeichert wird.
Verwenden von MutableHeapNumber-EntitätenInfolgedessen muss V8 nach Änderung des 
HeapNumber keinen Speicher mehr für die neue 
HeapNumber Entität 
HeapNumber . Schreiben Sie stattdessen einfach den neuen Wert in eine vorhandene 
MutableHeapNumber Entität.
Schreiben eines neuen Werts in MutableHeapNumberDieser Ansatz hat jedoch seine Nachteile. Da sich die Werte von 
MutableHeapNumber ändern können, ist es wichtig sicherzustellen, dass das System so funktioniert, dass sich diese Werte wie in der Sprachspezifikation angegeben verhalten.
Nachteile von MutableHeapNumberWenn Sie beispielsweise den Wert von 
ox anderen Variablen 
y zuweisen, müssen Sie sicherstellen, dass sich der Wert von 
y bei einer nachfolgenden Änderung von 
ox nicht ändert. Das wäre ein Verstoß gegen die JavaScript-Spezifikation! Daher muss beim Zugriff auf 
ox die Nummer auf den üblichen 
HeapNumber Wert neu 
HeapNumber werden, bevor sie 
y zugewiesen wird.
Im Fall von Gleitkommazahlen führt V8 die obigen Packoperationen unter Verwendung seiner internen Mechanismen aus. Bei kleinen Ganzzahlen wäre die Verwendung von 
MutableHeapNumber jedoch Zeitverschwendung, da 
Smi eine effizientere Methode zur Darstellung solcher Zahlen darstellt.
 const object = { x: 1 };  
Um eine ineffiziente Nutzung der Systemressourcen zu vermeiden, müssen wir für die Arbeit mit kleinen Ganzzahlen lediglich die entsprechenden Felder in Form von Objekten als 
Smi markieren. Infolgedessen können die Werte dieser Felder direkt innerhalb der Objekte aktualisiert werden, sofern sie dem 
Smi Bereich entsprechen.
Arbeiten Sie mit ganzen Zahlen, deren Werte im Bereich von Smi liegenFortsetzung folgt…
Liebe Leser! Haben Sie Probleme mit der JavaScript-Leistung festgestellt, die durch Funktionen der JS-Engine verursacht wurden?
