Detaillierte Diskussion der Speicherprinzipien: Werden Variablen in JS im Heap oder Stack gespeichert?

Detaillierte Diskussion der Speicherprinzipien: Werden Variablen in JS im Heap oder Stack gespeichert?

Werden primitive Typen in JavaScript auf dem Heap oder dem Stack gespeichert?

---- Grundtypen von Nicht-Grundtypen

Angesichts dieser Frage glaube ich, dass jeder das Gefühl hat, dass sie so grundlegend ist, dass sie noch grundlegender nicht sein kann. Durchsuchen Sie einfach Baidu und Sie werden viele Leute sehen, die sagen: Basistypen werden im Stapel gespeichert und Referenztypen im Heap.

Ist es wirklich so einfach?

1. Der Elefant, der nicht in den Kühlschrank passt

Schauen wir uns diesen Code an:

Hier deklarieren wir eine 67-MiB-Zeichenfolge, die schwer zu erklären wäre, wenn die Zeichenfolge tatsächlich auf dem Stapel vorhanden wäre. Schließlich beträgt die Standard-Stapelgröße von v8 984 KiB. Es ist definitiv nicht mehr zu retten.

Hinweis: V8 hat in verschiedenen Betriebssystemen und zu verschiedenen Zeiten unterschiedliche Beschränkungen der Zeichenfolgengröße. Der ungefähre Bereich beträgt 256 MiB ~ 1 GiB

Knoten --v8-Optionen | grep -B0 -A1 Stapelgröße


Beginnen Sie an diesem Punkt, sich zu wundern? Ist es möglich, dass die Antwort von Baidu falsch ist und ich bei Google suchen muss?

Mal sehen, was los ist.

2. Schattenklon-Zeichenfolge

const BasicVarGen = Funktion () {
    dies.s1 = "IAmString"
    dies.s2 = "IAmString"
}


lass a = neues BasicVarGen()
lass b = neues BasicVarGen()

Hier deklarieren wir zwei identische Objekte, die jeweils zwei identische Zeichenfolgen enthalten.

Durch die Entwicklertools können wir sehen, dass, obwohl wir vier Zeichenfolgen deklariert haben, ihr Speicher auf dieselbe Adresse verweist.

Hinweis: chrome kann die tatsächliche Adresse nicht anzeigen. Hier ist die abstrakte Adresse

Was bedeutet das? Es zeigt sich, dass die vier Zeichenfolgen Referenzadressen enthalten.

Der Elefant im obigen Artikel, der nicht in den Kühlschrank passt, ist also leicht zu erklären. Der String wird nicht im Stack gespeichert, sondern an einer anderen Stelle und die Adresse dieser Stelle wird dann im Stack gespeichert.

Ändern wir also den Inhalt einer der Zeichenfolgen.

const BasicVarGen = Funktion () {
    dies.s0 = "IAmString"
    dies.s1 = "IAmString"
}


lass a = neues BasicVarGen()
lass b = neues BasicVarGen()
Debugger
a.s0 = 'andere Zeichenfolge'
a.s2 = "IAmString"

Speicherabbild vor dem debugger

Speicherabbild nach dem debugger

Wir können sehen, dass der anfängliche Inhalt von a.s0 „ IAmString' ist, und nachdem wir den Inhalt geändert haben, ändert sich die Adresse.

Die neu hinzugefügte Variable a.s2 hat den Inhalt „ IAmString' und ihre Adresse stimmt mit anderen Variablen überein, deren Wert „ IAmString' ist.

Wenn wir eine Zeichenfolge deklarieren:

  1. In v8 gibt es eine hashmap namens stringTable , die alle Zeichenfolgen zwischenspeichert. Wenn V8 unseren Code liest und den abstrakten Syntaxbaum konvertiert, konvertiert es jedes Mal, wenn es auf eine Zeichenfolge stößt, diese basierend auf ihren Eigenschaften in einen hash und fügt sie in hashmap ein. Wenn später ein String mit gleichem Hashwert angetroffen wird, wird dieser zunächst zum Vergleich herausgenommen. Bei Übereinstimmung wird keine neue Stringklasse generiert.
  2. Beim Zwischenspeichern von Strings kommen je nach String unterschiedliche hash zum Einsatz.

Also lasst uns das klären. Wenn wir einen String erstellen, durchsucht V8 zuerst den Speicher (Hash-Tabelle), um zu sehen, ob es einen identischen String gibt, der bereits erstellt wurde. Wenn es ihn gibt, wird er direkt wiederverwendet. Wenn es nicht existiert, wird ein neuer Speicherplatz zum Speichern der Zeichenfolge geöffnet und anschließend die Adresse der Variablen zugewiesen. Aus diesem Grund können wir Zeichenfolgen nicht direkt mithilfe von Indizes ändern: Zeichenfolgen in V8 sind unveränderlich.

Nehmen Sie als Beispiel eine grundlegende Typkopie von js, um die Implementierungslogik von v8 und die herkömmliche Logik zu erklären, die jeder versteht (Yawen).

Beispiel:

var a = "刘潇洒"; // Nachdem V8 den String gelesen hat, sucht es in der String-Tabelle nach, ob er existiert. Wenn er nicht existiert, fügt es '刘潇洒' in die Hash-Tabelle ein und speichert die Referenz von '刘潇洒' in einem
var b = a; // Kopiere direkt die Referenz von '刘潇洒' b = "谭雅文"; // Suche nach keinem Eintrag in der Zeichenfolgentabelle


Fragen:

const BasicVarGen = Funktion () {
    dies.s0 = "IAmString"
    dies.s1 = "IAmString"
}


lass a = neues BasicVarGen()
lass b = neues BasicVarGen()
Debugger
a.s0 = 'andere Zeichenfolge'
a.s2 = "IAmString"


a.s3 = a.s2+a.s0; // Frage: Welche Operationen werden bei der Zeichenkettenverkettung ausgeführt?
a.s4 = a.s2+as

Gilt für zwei gleichzeitig verknüpfte Zeichenfolgen mit demselben Inhalt.

Wie Sie sehen, ist der Inhalt derselbe. Die Adressen sind jedoch nicht dieselben. Darüber hinaus hat sich auch die Kartenbeschreibung vor der Adresse geändert.

Wenn Zeichenfolgen auf herkömmliche Weise verknüpft werden (z. B. SeqString ), beträgt die zeitliche Komplexität der Verknüpfungsoperation O(n). Die Verwendung Rope Structure (d. h. der von ConsString verwendeten Datenstruktur) kann den Zeitaufwand für die Verknüpfung verringern.

Wenn dies für Zeichenfolgen gilt, gilt es dann auch für andere primitive Typen?

3. Der „seltsame Ball“, den ich persönlich sehe

Nachdem wir über Zeichenfolgen gesprochen haben, schauen wir uns einen anderen typischen „Basistyp“ in V8 an: oddBall .

Erweitert vom type oddBall

Machen wir noch ein kleines Experiment:

Wir können die in der Abbildung oben aufgeführten Grundtypen sehen und die Adressen sind dieselben. Auch bei der Wertezuweisung werden diese an Ort und Stelle wiederverwendet. (Und die Adressen dieser von oddBall erweiterten Basistypen sind festgelegt, d. h. wenn V8 zum ersten Mal ausgeführt wird, wurden sie erstellt, unabhängig davon, ob wir diese Basistypen deklarieren oder nicht. Wenn wir Objekte deklarieren, weisen wir ihre Referenzen zu. Dies erklärt auch, warum wir sagen, dass Basistypen dem Stapel zugewiesen werden: In V8 ist der in @73 gespeicherte Wert immer eine leere Zeichenfolge, sodass v8 diese Adressen als die Werte selbst behandeln kann.)

Schauen wir uns zur Überprüfung den Quellcode an:

Generieren Sie verschiedene oddBall -Typen von Methoden, Sie können sehen, dass die Rückgabe eine Adresse ist

Wenn einer Variablen undefined zugewiesen wird, wird ihr tatsächlich die Adresse zugewiesen

getRoot -Methode

Wo der Offset definiert ist

4. Verwirrende Zahlen

Der Grund, warum diese Zahl als verwirrend bezeichnet wird, liegt darin, dass der Mechanismus der Speicherzuweisung bei Zuweisung und Änderung noch nicht geklärt ist. (Sein Gedächtnis ist dynamisch)

Zahlen werden in V8 in smi und heapNumber unterteilt.

smi speichert direkt ganze Zahlen im Bereich von -2³¹ bis 2³¹-1 (2³¹≈2*10⁹)

heapNumber ist ähnlich wie string und unveränderlich. Sein Gültigkeitsbereich ist: alle Nicht-SMI-Zahlen

Das niedrigste Bit dient zur Anzeige, ob es sich um einen Zeiger handelt. Wenn das niedrigste Bit 1 ist, handelt es sich um einen Zeiger.

Konstante o = {
  x: 42, // Smi
  y: 4.2, // HeapNummer
};


Die 42 in ox werden als Smi behandelt und direkt im Objekt selbst gespeichert, während die 4,2 in oy in einem zusätzlichen Speicherobjekt gespeichert werden müssen und der Objektzeiger von oy auf das Speicherobjekt zeigt.

Wenn es sich um ein 32-Bit-Betriebssystem handelt, ist es verständlich, 32 Bit zur Darstellung von SMI zu verwenden. Warum liegt der SMI-Bereich jedoch bei einem 64-Bit-Betriebssystem auch zwischen -2³¹ und 2³¹-1 (2³¹≈2*10⁹)?

ECMAScript Standard legt fest, dass number als 64-Bit-Gleitkommazahlen mit doppelter Genauigkeit behandelt werden müssen. Tatsächlich ist es jedoch sehr ineffizient, immer 64 Bit zum Speichern beliebiger Zahlen zu verwenden (Platzmangel, Rechenzeitmangel smi verwendet viele Bitoperationen), sodass die JavaScript Engine nicht immer 64 Bit zum Speichern von Zahlen verwendet. Die Engine kann intern andere Speicherdarstellungen (z. B. 32 Bit) verwenden, solange alle externen Merkmale der Zahl, die überwacht werden können, mit der 64-Bit-Darstellung übereinstimmen.

Konstantes Zykluslimit = 50000
Konsole.Zeit('Heapnummer')
const foo = { x: 1.1 };
für (sei i = 0; i < Zyklusgrenze; ​​++i) {
// Erstellt eine zusätzliche HeapNumber-Instanz foo.x += 1; 
}
console.timeEnd('heapNumber') // langsam   


Konsole.Zeit('smi')
const bar = { x: 1.0 };
für (sei i = 0; i < Zyklusgrenze; ​​++i) {
  bar.x += 1;
}
console.timeEnd('smi') // schnell

Fragen:

const BasicVarGen = Funktion () {


    dies.smi1 = 1
    dies.smi2 = 2
    diese.Heapnummer1 = 1,1
    diese.Heapnummer2 = 2,1
}
    let foo = neues BasicVarGen()
    let bar = new BasicVarGen()
    
    Debugger
    
    baz.obj1.heapNumber1++

Bei den Zahlen wird der Wert einer einzelnen Zahl nicht verändert, die Adressen anderer Zahlen werden jedoch geändert.

5. Zusammenfassung: Wo gibt es Basistypen?

String: Er existiert im Heap und ist eine Referenzadresse im Stack. Wenn derselbe String existiert, ist die Referenzadresse dieselbe.

Zahlen: Kleine Ganzzahlen werden auf dem Stapel gespeichert, andere Typen auf dem Heap.

Andere Typen: Beim Initialisieren der Engine wird eine eindeutige Adresse zugewiesen und die Variablen im Stapel speichern eindeutige Referenzen.

Hier kann nur grob erklärt werden, wo es die Grundtypen gibt.

Damit ist dieser Artikel zu detaillierten Speicherprinzipien und der Frage, ob Variablen in JS im Heap oder im Stack gespeichert werden, abgeschlossen. Weitere Informationen dazu, ob Variablen in JS im Heap oder im Stack gespeichert werden, finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!

Das könnte Sie auch interessieren:
  • Detaillierte Erläuterung der Deep Copy und Shallow Copy im JS-Variablenspeicher
  • Beispielanalyse für JS-Variablenförderung und Funktionsförderung
  • Detaillierte Erklärung von JS-Variablen, Umfang und Speicher
  • Detaillierte Erklärung der JS-Variablen und ihres Umfangs
  • Ein kurzer Vortrag über die Variablenförderung in JavaScript
  • Alle Eigenschaften des variablen Dom-Objekts von JavaScript
  • Grundlegende Verwendung von JavaScript-Variablen
  • Beispielanalyse für die Deklaration von JavaScript-Variablen
  • Detaillierte Erklärung des Javascript-Variablenbereichs und der Bereichskette

<<:  So führen Sie Linux-Befehle im Hintergrund aus

>>:  Allgemeine Lösungen für das Ablaufen der Lese-/Schreibtrennung in MySQL

Artikel empfehlen

Vor- und Nachteile des Tabellenlayouts und warum es nicht empfohlen wird

Nachteile von Tabellen 1. Tabellen nehmen mehr Byt...

Detaillierte Erklärung des FreeList-Mechanismus von MySQL

1. Einleitung Nach dem Start von MySQL wird Buffe...

So fragen Sie einen Datensatz in MySQL ab, auf welcher Seite der Paging-Seite

Vorwort In der Praxis kann es zu folgendem Proble...

Dynamische SQL-Anweisungsanalyse in Mybatis

Dieser Artikel stellt hauptsächlich die dynamisch...

Allgemeine Datumsvergleichs- und Berechnungsfunktionen in MySQL

Implementierung des Zeitvergleichs in MySql unix_...

Kreatives „Über uns“-Webseitendesign

Einzigartige „Über“-Seiten Eine gute Möglichkeit, ...

Tipps zum Erstellen von Webseiten für Mobiltelefone

Angesichts der Tatsache, dass mittlerweile viele M...

So stellen Sie Tencent Cloud Server von Grund auf bereit

Da dies mein erster Beitrag ist, weisen Sie mich ...

Zusammenfassung der MySQL-DML-Anweisungen

DML-Operationen beziehen sich auf Operationen an ...

...

HTML-Grundlagen - Zusammenfassung - Empfehlung (Absatz)

HTML-Absatz Absätze werden durch das Tag <p>...