Lassen Sie uns im Detail darüber sprechen, wie Browser Abschlüsse betrachten

Lassen Sie uns im Detail darüber sprechen, wie Browser Abschlüsse betrachten

Vorwort

Closures stellen eine große Schwierigkeit beim Verständnis von JavaScript dar. Im Internet gibt es viele Artikel über Closures, aber nur wenige Artikel vermitteln den Leuten nach der Lektüre ein vollständiges Verständnis. Der Grund hierfür liegt meiner Meinung nach darin, dass der Abschluss eine Reihe von Wissenspunkten beinhaltet. Nur wenn Sie diese Reihe von Wissenspunkten gründlich durchschauen und ein geschlossenes Konzept erreichen, können Sie es wirklich verstehen. Heute möchte ich Closures aus einer anderen Perspektive verstehen, nämlich aus der Perspektive der Speicherzuweisung und -wiederverwendung. Ich hoffe, Ihnen dabei zu helfen, das Wissen über Closures, das Sie gesehen haben, wirklich zu verarbeiten. Gleichzeitig hoffe ich auch, dass dieser Artikel der letzte Artikel ist, den Sie über Closures lesen.

Beachten Sie beim Betrachten der Bilder in diesem Artikel bitte die Richtung der Pfeile. Da dies das Prinzip ist, auf dem das Stammobjektfenster zum Durchlaufen von Speichermüll beruht, ist alles, was entlang des Pfeils gefunden werden kann, der vom Fenster ausgeht, kein Speichermüll und wird nicht wiederverwendet. Nur die Objekte, die nicht gefunden werden können, sind Speichermüll und werden zum entsprechenden Zeitpunkt von GC wiederverwendet.

Einführung in Closures

Wenn Funktionen verschachtelt sind, verweist die innere Funktion auf die Variablen im äußeren Funktionsumfang, und die innere Funktion wird von den Variablen in der globalen Umgebung referenziert, wodurch ein Abschluss gebildet wird.

Closures sind im Wesentlichen ein Nebenprodukt des Funktionsumfangs.

Bei Closures müssen wir besonders darauf achten, dass alle innerhalb einer Funktion definierten Funktionen dasselbe Closure-Objekt gemeinsam nutzen. Was bedeutet das? Sehen Sie sich den folgenden Code an:

var ein
Funktion b() {
  var c = neuer String('1')
  var d = neuer String('2')
  Funktion e() {
    console.log(c)
  }
  Funktion f() {
    console.log (d)
  }
  Rückkehr f
}
a = b()

Im obigen Code verweist f auf die Variable d und auf f wird durch die externe Variable a verwiesen, sodass ein Abschluss gebildet wird, der bewirkt, dass die Variable d im Speicher verbleibt. Lassen Sie uns darüber nachdenken: Was ist mit der Variable c? Es scheint, dass wir C nicht verwendet haben, also sollte es nicht im Speicher bleiben. Hinzu kommt, dass c auch in Erinnerung bleiben wird. Der durch den obigen Code gebildete Abschluss enthält zwei Mitglieder, c und d. Dieses Phänomen wird als Intra-Function-Closure-Sharing bezeichnet.

Warum müssen wir dieser Funktion besondere Aufmerksamkeit schenken? Aufgrund dieser Funktion ist es leicht, Code zu schreiben, der Speicherlecks verursacht, wenn wir nicht vorsichtig sind.
Ich habe so viel über das Konzept von Closures gesagt, aber wenn Sie Closures wirklich verstehen möchten, müssen Sie noch einige Wissenspunkte verstehen.

  • Funktionsumfangskette
  • Ausführungskontext
  • Variablenobjekt, Aktivitätsobjekt

Sie können diese Inhalte bei Google oder Baidu suchen, um sich einen allgemeinen Überblick zu verschaffen. Als Nächstes werde ich darüber sprechen, wie man Closures aus der Perspektive des Browsers versteht, daher werde ich nicht im Detail darauf eingehen.

So identifizieren Sie Speichermüll

Der Garbage Collection-Prozess moderner Browser ist relativ kompliziert. Den detaillierten Vorgang können Sie selbst googeln. Hier werde ich nur darauf eingehen, wie man Speichermüll ermittelt. Im Allgemeinen ist davon auszugehen, dass ausgehend vom Stammobjekt alles, was durch Folgen der Referenz gefunden werden kann, nicht wiederverwendet werden kann. Objekte, die durch Befolgen der Referenz nicht gefunden werden können, gelten als Müll und werden am nächsten Garbage Collection-Knoten wiederverwendet. Die Suche nach Müll kann als ein Prozess des Verfolgens von Hinweisen verstanden werden.

Speicherdarstellung von Closures

Beginnen wir mit dem einfachsten Code und schauen uns die Definition der globalen Variablen an.

var a = neuer String('a')

Ein solcher Code wird im Speicher wie folgt dargestellt

In der globalen Umgebung wird eine Variable a definiert und ihr ein String zugewiesen. Der Pfeil zeigt eine Referenz an.

Definieren wir eine andere Funktion:

var a = neuer String('a')
Funktion lehren() {
  var b = neuer String('Zeichenfolge')
}

Die Speicherstruktur ist wie folgt:

Alles ist leicht zu verstehen. Wenn Sie genau hinschauen, werden Sie feststellen, dass es im Funktionsobjekt teach ein Attribut namens [[scopes]] gibt. Was ist das? Warum wird dieses Attribut angezeigt, nachdem die Funktion erstellt wurde? Ich bin froh, dass Sie das gefragt haben. Es ist ein wichtiger Punkt zum Verständnis von Closures.

Bitte beachten Sie: Sobald eine Funktion erstellt wurde, fügt die JavaScript-Engine dem Funktionsobjekt eine Eigenschaft namens „Scope Chain“ hinzu. Diese Eigenschaft verweist auf ein Array-Objekt, das den Gültigkeitsbereich und den übergeordneten Gültigkeitsbereich der Funktion bis hin zum globalen Gültigkeitsbereich enthält.

Daher kann die obige Abbildung einfach wie folgt verstanden werden: Die Teach-Funktion wird in der globalen Umgebung erstellt, sodass die Gültigkeitsbereichskette von Teach nur eine Ebene hat, nämlich den globalen Gültigkeitsbereich global

Es sollte klar sein, dass sich „global“ im Browser auf das Fensterobjekt bezieht und sich „global“ in der Node.JS-Umgebung auf das globale Objekt bezieht.

Bitte denken Sie noch einmal daran: Wenn eine Funktion ausgeführt wird, wird Platz benötigt, um einen Ausführungskontext zu erstellen. Der Ausführungskontext umfasst die Bereichskette, wenn die Funktion definiert ist, und zweitens die Variablen und Parameter, die innerhalb der Funktion definiert sind. Wenn die Funktion im aktuellen Bereich ausgeführt wird, sucht sie zuerst nach Variablen im aktuellen Bereich. Wenn sie nicht gefunden werden kann, sucht sie in der Bereichskette, wenn die Funktion definiert ist, bis sie den globalen Bereich erreicht. Wenn die Variable im globalen Bereich nicht gefunden werden kann, wird ein Fehler ausgegeben.

Wir alle wissen, dass bei der Ausführung einer Funktion ein Ausführungskontext erstellt wird, der eigentlich einen Speicherplatz einer Stapelstruktur beantragt. Die lokalen Variablen in der Funktion werden in diesem Speicherplatz zugewiesen. Nachdem die Funktion ausgeführt wurde, werden die lokalen Variablen beim nächsten Garbage Collection-Knoten wiederverwendet. OK, aktualisieren wir den Code noch einmal und werfen einen Blick auf die Speicherstruktur, wenn die Funktion ausgeführt wird.

var a = neuer String('a')
Funktion lehren() {
  var b = neuer String('Zeichenfolge')
}
unterrichten()

Der Speicher wird wie folgt dargestellt:

Offensichtlich können wir sehen, dass die Funktion während der Ausführung nur eine lokale Variable zuweist und keine Beziehung zu den Variablen in der globalen Umgebung hat. Wenn wir daher vom Fensterobjekt aus entlang der Referenz (Pfeil in der Abbildung) suchen, können wir die Variable b im Ausführungskontext nicht finden. Daher wird die Variable b nach Ausführung der Funktion wiederverwendet.

Lassen Sie uns den Code noch einmal aktualisieren:

var a = neuer String('a')
Funktion lehren() {
  var b = neuer String('Zeichenfolge')
  var say = Funktion() {
    console.log(b)
  }
  a = sagen
}
unterrichten()

Der Speicher wird wie folgt dargestellt:

Hinweis: Grau kennzeichnet Objekte, die nicht vom Stammobjekt aus verfolgt werden können.

Reihenfolge der Funktionsausführung:

  1. Bevor die Teach-Funktion ausgeführt wird, wird Stapelspeicherplatz angefordert, wie im blauen Quadrat in der Abbildung oben dargestellt.
  2. Erstellen Sie einen Kontextbereich (eine stapelähnliche Struktur) und fügen Sie die in der Teach-Funktion definierten [[Bereiche]] in den Bereich ein.
  3. Initialisieren Sie die Variable b (Variablenförderung), erstellen Sie die Funktion say, initialisieren Sie die Scopes-Eigenschaft von say und übertragen Sie zuerst die Scopes der Funktion teach in die [[Scopes]] der Funktion say. Da sich say auf die Variable b bezieht, wird ein Abschluss gebildet. Daher müssen wir das Closure-Objekt beispielsweise auch in die [[Scopes]] der Funktion verschieben.
  4. Erstellen Sie ein variables Objekt „local“, um auf lokale Variablen „b“ zu verweisen, und verschieben Sie „local“ in den Bereich von Schritt 2.
  5. Die Funktion wird ausgeführt
    1. Weisen Sie der Variablen b das Zeichenfolgenobjekt „Xiaogu“ zu.
    2. Legen Sie die globale Variable „a“ so fest, dass sie auf die Funktion „say“ zeigt.

Nachdem die Funktion ausgeführt wurde, sollte die Variable b unter normalen Umständen freigegeben werden. Wir stellen jedoch fest, dass wir b finden können, indem wir entlang des Fensters suchen. Gemäß dem zuvor erwähnten Prinzip zur Ermittlung von Speichermüll ist b kein Speichermüll und kann daher nicht freigegeben werden. Aus diesem Grund behalten Closures Variablen innerhalb von Funktionen im Speicher.

Aktualisieren wir den Code noch einmal und sehen wir uns die vom Abschluss gemeinsam genutzte Speicherdarstellung an:

var a = neuer String('0')
Funktion b() {
  var c = neuer String('1')
  var d = neuer String('2')
  Funktion e() {
    console.log(c)
  }
  Funktion f() {
    console.log (d)
  }
  Rückkehr f
}
a = b()

Die grauen Grafiken sind Speichermüll und werden vom Garbage Collector wiederverwendet.

Aus der obigen Abbildung ist leicht ersichtlich, dass die Funktion f zwar die Variable c nicht verwendet, c aber von der Funktion e referenziert wird, sodass die Variable c im Abschlussabschluss vorhanden ist. Die Variable c kann gefunden werden, indem man vom Fensterobjekt ausgeht, sodass die Variable c nicht freigegeben werden kann.

Sie fragen sich vielleicht, wie diese Funktion zu Speicherlecks führen kann? Betrachten Sie den folgenden Code, ein klassisches Meteor-Speicherleckproblem.

        var t = null;
        var replaceThing = Funktion() {
            var o = t
            var unbenutzt = function() {
                wenn (o)
                    console.log("hallo")
            }
            t = {
                    longStr: neues Array(1000000).join('*'),
                    irgendeineMethode: function() {
                      console.log(1)
                    }
                }
        }
        setzeInterval(Ersatzteil, 1000)

Dieser Code weist einen Speicherverlust auf. Wenn Sie diesen Code im Browser ausführen, werden Sie feststellen, dass der Speicher weiter ansteigt. Obwohl GC etwas Speicher freigibt, gibt es immer noch etwas Speicher, der nicht freigegeben werden kann, und dieser steigt in einem Gradienten an. Wie unten gezeigt

Diese Kurve weist darauf hin, dass ein Speicherverlust vorliegt. Mithilfe von Entwicklertools können wir analysieren, welche Objekte nicht wiederverwendet wurden. Tatsächlich kann ich Ihnen sagen, dass der Speicher, der nicht freigegeben wird, eigentlich das große Objekt ist, das wir jedes Mal erstellen. Schauen wir uns das an, indem wir ein Bild zeichnen:

Das obige Bild geht davon aus, dass die Funktion replaceThing dreimal ausgeführt wird. Sie werden feststellen, dass jedes Mal, wenn wir der Variablen t ein großes Objekt zuweisen, aufgrund der gemeinsamen Nutzung von Closure das vorherige große Objekt immer noch vom Fensterobjekt aus verfolgt werden kann, sodass diese großen Objekte nicht wiederverwendet werden können. Was für uns tatsächlich wirklich nützlich ist, ist das große Objekt, das t beim letzten Mal zugewiesen wurde, sodass das vorherige Objekt einen Speicherverlust verursacht hat.

Wie Sie sich vorstellen können, stürzt der Browser bald ab, wenn wir dies nicht beachten und das Programm laufen lassen.

Die Lösung für dieses Problem ist auch sehr einfach. Setzen Sie bei jeder Ausführung des Codes die Variable o auf null. Sie können es versuchen.

Abschluss

Dies ist das Ende dieses Artikels darüber, wie Browser Closures betrachten. Weitere Informationen dazu, wie Browser Closures betrachten, 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:
  • JavaScript - tiefgreifendes Verständnis von JS-Closure
  • JS-Closures in einer Minute verstehen
  • JS-Closures und -Anwendungen, die Frontend-Entwickler kennen müssen
  • Detaillierte Erklärung zum Schreiben und Verwenden von Closures in JavaScript
  • Es liegt an Ihnen, ob Sie JavaScript-Closures verstehen oder nicht. Ich verstehe es jedenfalls.
  • Detaillierte Erläuterung der Verwendung von JS-Verschlüssen

<<:  display:grid in CSS3, eine Einführung in das Rasterlayout

>>:  Detaillierte Erklärung zur Verwendung des Iframe-Tags (Attribute, Transparenz, adaptive Höhe)

Artikel empfehlen

Zabbix-Überwachungslösung – die neueste offizielle Version 4.4 [empfohlen]

Zabbix 12.10.2019 Chenxin siehe https://www.zabbi...

Verwendung des Linux-Befehls sed

1. Funktionseinführung sed (Stream EDitor) ist ei...

So verwenden Sie Javascript zum Generieren glatter Kurven

Inhaltsverzeichnis Vorwort Einführung in Bézierku...

So verwenden Sie Vue zum Entwickeln öffentlicher Account-Webseiten

Inhaltsverzeichnis Projekthintergrund Start Erste...

Erläuterung der HTML-Tags

Erläuterung der HTML-Tags 1. HTML-Tags Tag: !DOCT...

Gegenfall für die Vue-Implementierung

In diesem Artikelbeispiel wird der spezifische Co...

Einführung in general_log-Protokollwissenspunkte in MySQL

Die folgenden Funktionsdemonstrationen basieren a...

MySQL 8.0 kann jetzt JSON verarbeiten

Inhaltsverzeichnis 1. Kurzübersicht 2. JSON-Grund...