Heim  >  Artikel  >  Web-Frontend  >  Beherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständig

Beherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständig

WBOY
WBOYnach vorne
2022-04-25 17:05:203128Durchsuche

Dieser Artikel vermittelt Ihnen relevantes Wissen über Javascript. Er stellt hauptsächlich Probleme im Zusammenhang mit dem Funktionsmechanismus und den Prinzipien von JavaScript vor, einschließlich Parsing-Engines und anderen Inhalten. Ich hoffe, dass er für alle hilfreich ist.

Beherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständig

[Verwandte Empfehlungen: Javascript-Video-Tutorial, Web-Frontend]

Ich schreibe seit mehr als zwei Jahren JS und habe seinen Funktionsmechanismus und seine Prinzipien nie verstanden Zusammenfassend sind die Theorien und Prinzipien der Meister aufgeführt die Codeausführung.

Wenn Sie beispielsweise einen Code wie var a = 1 + 1; schreiben, versteht (analysiert) die JavaScript-Engine Ihren Code und ändert den Wert von a in 2. Wer die Prinzipien der Kompilierung studiert hat, weiß, dass für statische Sprachen (wie Java, C++, C) derjenige, der die oben genannten Dinge verarbeitet, als Compiler (Compiler) bezeichnet wird, und entsprechend für dynamische Sprachen ​​​​wie JavaScript wird es als Interpreter (Interpreter) bezeichnet). Der Unterschied zwischen den beiden lässt sich in einem Satz zusammenfassen: Der Compiler kompiliert den Quellcode in einen anderen Codetyp (z. B. Maschinencode oder Bytecode), während der Interpreter die Ergebnisse der Codeausführung direkt analysiert und ausgibt. Beispielsweise ist die Firebug-Konsole ein JavaScript-Interpreter.

Allerdings ist es jetzt schwierig zu definieren, ob die JavaScript-Engine ein Interpreter oder ein Compiler ist, da beispielsweise V8 (die JS-Engine von Chrome) tatsächlich, um die Laufleistung von JS zu verbessern, JS zunächst in native kompiliert Maschinencode erstellen und dann den Maschinencode ausführen (das ist viel schneller).

Welche Beziehung besteht zwischen der JavaScript-Parsing-Engine und ECMAScript?

Die JavaScript-Engine ist ein Programm, und der von uns geschriebene JavaScript-Code ist auch ein Programm. Dafür müssen Regeln definiert werden. Beispielsweise bedeutet die zuvor erwähnte Variable a = 1 + 1;:

Die Variable links stellt eine Deklaration dar, die die Variable a deklariert Das Gleichheitszeichen zeigt an, dass es sich um eine Zuweisungsanweisung handelt. Das letzte Semikolon gibt das Ende dieser Anweisung an. Mit ihnen gibt es einen Standard für die Messung. Die JavaScript-Engine kann den JavaScript-Code gemäß diesem Standard analysieren. Dann definiert das ECMAScript hier diese Regeln. Darunter definiert das Dokument ECMAScript 262 einen vollständigen Satz von Standards für die JavaScript-Sprache. Dazu gehören:

var, if, else, break, continue usw. sind JavaScript-Schlüsselwörter

abstract, int, long usw. sind für JavaScript reservierte Wörter

Zählen als Zahl, Zählen als String usw.

Definieren Sie den Operator ( +, -, >, Definiert die Syntax von JavaScript.
Definiert Standardverarbeitungsalgorithmen für Ausdrücke, Anweisungen usw., z. B. die Handhabung von ==.
⋯⋯
Der Standard Die JavaScript-Engine wird zum Implementieren dieser Dokumente verwendet. Beachten Sie, dass hier Standards hervorgehoben werden, da es auch Implementierungen gibt, die nicht den Standards folgen, wie beispielsweise die JS-Engine des IE. Aus diesem Grund gibt es bei JavaScript Kompatibilitätsprobleme. Auf die Frage, warum die JS-Engine des IE nicht standardgemäß implementiert ist, gehe ich hier nicht näher ein.
Einfach ausgedrückt definiert ECMAScript den Standard der Sprache und die JavaScript-Engine implementiert ihn entsprechend. Dies ist die Beziehung zwischen den beiden.


Welche Beziehung besteht zwischen der JavaScript-Parsing-Engine und dem Browser?

Einfach ausgedrückt ist die JavaScript-Engine eine der Komponenten des Browsers. Weil der Browser viele andere Dinge tun muss, wie z. B. das Parsen von Seiten, das Rendern von Seiten, die Cookie-Verwaltung, Verlaufsaufzeichnungen usw. Nun, da es sich um eine Komponente handelt, werden JavaScript-Engines im Allgemeinen von Browserentwicklern selbst entwickelt. Zum Beispiel: Chakra von IE9, TraceMonkey von Firefox, V8 von Chrome usw.

Es ist auch zu erkennen, dass verschiedene Browser unterschiedliche JavaScript-Engines verwenden. Wir können also nur sagen, welche JavaScript-Engine wir besser verstehen sollten.

Warum JavaScript Single-Threaded ist

Ein Hauptmerkmal der JavaScript-Sprache ist, dass sie Single-Threaded ist, das heißt, sie kann jeweils nur eine Sache ausführen. Warum kann JavaScript also nicht mehrere Threads haben? Dadurch kann die Effizienz verbessert werden.

JavaScript ist je nach Verwendungszweck Single-Threaded. Als Browser-Skriptsprache besteht der Hauptzweck von JavaScript darin, mit Benutzern zu interagieren und das DOM zu manipulieren. Dies legt fest, dass es nur Single-Threaded sein kann, da es sonst zu sehr komplexen Synchronisationsproblemen kommt. Angenommen, JavaScript verfügt über zwei Threads gleichzeitig. Ein Thread fügt Inhalt zu einem bestimmten DOM-Knoten hinzu und der andere Thread löscht den Knoten. Welchen Thread sollte der Browser in diesem Fall verwenden?

Um Komplexität zu vermeiden, ist JavaScript seit seiner Geburt Single-Threaded. Dies ist zum Kernmerkmal dieser Sprache geworden und wird sich auch in Zukunft nicht ändern.

Um die Rechenleistung von Multi-Core-CPUs zu nutzen, schlägt HTML5 den Web Worker-Standard vor, der es JavaScript-Skripten ermöglicht, mehrere Threads zu erstellen, die untergeordneten Threads werden jedoch vollständig vom Haupt-Thread gesteuert und dürfen das DOM nicht bedienen . Daher ändert dieser neue Standard nichts an der Single-Threaded-Natur von JavaScript.

Unterscheiden Sie zwischen Prozessen und Threads

Ein Prozess ist die kleinste Einheit der CPU-Ressourcenzuweisung und ein Prozess kann mehrere Threads enthalten. Browser sind Multiprozess-Browser und jedes geöffnete Browserfenster ist ein Prozess.

Thread ist die kleinste Einheit der CPU-Planung. Der Speicherplatz des Programms wird von Threads im selben Prozess gemeinsam genutzt.

Sie können sich den Prozess wie ein Lager vorstellen, und der Thread ist ein LKW, der transportiert werden kann. Jedes Lager verfügt über mehrere eigene LKWs, die das Lager bedienen (jedes Lager kann von mehreren Fahrzeugen gleichzeitig gezogen werden). Zeit, aber jedes Lager kann gleichzeitig Waren transportieren. Ein Fahrzeug kann jeweils nur eine Aufgabe erfüllen, nämlich diese Fracht zu transportieren.

Kernpunkt:

Ein Prozess ist die kleinste Einheit der CPU-Ressourcenzuweisung (die kleinste Einheit, die Ressourcen besitzen und unabhängig laufen kann)

Thread ist die kleinste Einheit der CPU-Planung (ein Thread ist ein Programm, das auf a Prozess) Laufende Einheit, es können mehrere Threads in einem Prozess sein)

Verschiedene Prozesse können auch kommunizieren, aber die Kosten sind höher.
Browser sind Multiprozess-Browser
Nachdem wir den Unterschied zwischen Prozessen und Threads verstanden haben, wollen wir uns ein gewisses Verständnis von Browsern verschaffen: (Sehen wir uns zunächst das vereinfachte Verständnis an)

Browser sind Multiprozess-Browser

Der Grund, warum Browser ausgeführt werden können weil das System seinem Prozess Ressourcen (CPU, Speicher) zuweist. Vereinfacht ausgedrückt entspricht dies jedes Mal, wenn eine Registerkarte geöffnet wird, dem Erstellen eines unabhängigen Browserprozesses.

Nehmen Sie Chrome als Beispiel, und Sie können mehrere Prozesse im Task-Manager von Chrome sehen (jede Tab-Seite hat einen unabhängigen Prozess und einen Hauptprozess, der auch im Task-Manager angezeigt wird). .

Hinweis: Der Browser sollte auch hier über einen eigenen Optimierungsmechanismus verfügen. Nach dem Öffnen mehrerer Tab-Seiten können Sie im Chrome Task-Manager sehen, dass einige Prozesse zusammengeführt werden (also muss nicht jede Tab-Beschriftung einem Prozess entsprechen). Seien Sie absolut)

Welche Prozesse enthält der Browser?

Nachdem wir wissen, dass der Browser ein Multiprozess ist, schauen wir uns an, welche Prozesse er enthält: (Um das Verständnis zu vereinfachen, werden nur die Hauptprozesse aufgeführt)

( 1) Browserprozess: Es gibt nur einen Hauptprozess des Browsers (verantwortlich für Koordination und Steuerung). Rolle:

Verantwortlich für die Anzeige der Browseroberfläche und Benutzerinteraktion. Wie vorwärts, rückwärts usw.
  • Verantwortlich für die Verwaltung jeder Seite, Erstellung und Zerstörung anderer Prozesse
  • Zeichnen Sie die Bitmap im vom Renderer-Prozess erhaltenen Speicher auf die Benutzeroberfläche
  • Verwaltung von Netzwerkressourcen, Downloads, usw.
  • (2) Plug-in-Prozess von Drittanbietern: Jeder Plug-in-Typ entspricht einem Prozess, der nur erstellt wird, wenn das Plug-in verwendet wird.
(3) GPU-Prozess: höchstens einer, der verwendet wird für 3D-Zeichnungen usw.

(4) Browser-Rendering-Prozess (Browser-Kernel, Renderer-Prozess, intern multithreaded): Standardmäßig verfügt jede Tab-Seite über einen Prozess und beeinflusst sich nicht gegenseitig. Die Hauptfunktionen sind: Seitenrendering, Skriptausführung, Ereignisverarbeitung usw.

Speicher verbessern: Das Öffnen einer Webseite im Browser entspricht dem Starten eines neuen Prozesses (der Prozess verfügt über eigene Multithreads)

Natürlich Der Browser führt manchmal mehrere Prozesse zusammen (z. B. werden Sie nach dem Öffnen mehrerer leerer Registerkarten feststellen, dass mehrere leere Registerkarten zu einem Prozess zusammengeführt wurden)

Vorteile von Browsern mit mehreren Prozessen

Im Vergleich zu Browsern mit nur einem Prozess und mehreren Prozessen sind wie folgt Vorteile:

Vermeiden Sie einen Absturz einer einzelnen Seite, der sich auf den gesamten Browser auswirkt.
  • Vermeiden Sie Abstürze von Plug-Ins von Drittanbietern, die sich auf den gesamten Browser auswirken.
  • Mehrere Prozesse nutzen die Multi-Core-Vorteile voll aus.
  • Nutzen Sie bequem das Sandbox-Modell um Plug-Ins und andere Prozesse zu isolieren, um die Browserstabilität zu verbessern.
  • Einfaches Verständnis:

Wenn der Browser ein einzelner Prozess ist, wirkt sich der Absturz einer bestimmten Registerkarte auf den gesamten Browser aus. Wie schlecht wird die Erfahrung sein? Handelt es sich um einen einzelnen Prozess, wirkt sich der Absturz des Plug-Ins auch auf den gesamten Browser aus.

Natürlich wird auch der Speicher- und andere Ressourcenverbrauch größer sein, was bedeutet, dass Raum gegen Zeit getauscht wird. Egal wie groß der Speicher ist, er reicht für Chrome nicht aus. Das Problem des Speicherverlusts ist nur eine Verbesserung und wird auch zu einem erhöhten Stromverbrauch führen.

Browser-Kernel (Rendering-Prozess)

Hier kommt der entscheidende Punkt. Wir können sehen, dass es so viele Prozesse gibt, die oben erwähnt wurden. Was ist also die endgültige Anforderung? Die Antwort ist der Rendering-Prozess.

Es versteht sich, dass das Rendern der Seite, die Ausführung von JS und die Ereignisschleife alle innerhalb dieses Prozesses ausgeführt werden. Konzentrieren Sie sich als Nächstes auf die Analyse dieses Prozesses.

Bitte beachten Sie, dass der Rendering-Prozess des Browsers multithreaded ist (die JS-Engine ist Single-Threaded).

Schauen wir uns dann an, welche Threads er enthält (listen Sie einige Hauptresidenten auf). Threads ):

1. GUI-Rendering-Thread

  • ist verantwortlich für das Rendern der Browseroberfläche, das Parsen von HTML, CSS, den Aufbau des DOM-Baums und des RenderObject-Baums (einfach verstanden als der von CSS gebildete Stilbaum, einer der Kerne von Flutter), das Layout und das Zeichnen , usw.
  • Wenn die Schnittstelle neu gezeichnet werden muss (Repaint) oder ein Reflow durch einen bestimmten Vorgang verursacht wird, wird dieser Thread ausgeführt.
  • Beachten Sie, dass sich der GUI-Rendering-Thread und der JS-Engine-Thread gegenseitig ausschließen , wird der GUI-Thread angehalten (eingefroren) und die GUI-Updates werden in einer Warteschlange gespeichert und sofort ausgeführt, wenn die JS-Engine inaktiv ist.

2. Der JS-Engine-Thread

  • wird auch JS-Kernel genannt und ist für die Verarbeitung von Javascript-Skriptprogrammen verantwortlich. (Zum Beispiel V8-Engine)
  • Der JS-Engine-Thread ist für das Parsen von Javascript-Skripten und das Ausführen von Code verantwortlich.
  • Die JS-Engine wartet auf das Eintreffen von Aufgaben in der Aufgabenwarteschlange und verarbeitet sie dann. Es gibt immer nur einen JS-Thread, der das JS-Programm auf einer Tab-Seite (Renderer-Prozess) ausführt.
  • Beachten Sie auch, dass die Der GUI-Rendering-Thread und der JS-Engine-Thread schließen sich gegenseitig aus. Wenn also die Ausführungszeit von JS zu lang ist, ist das Rendern der Seite inkohärent, was dazu führt, dass das Rendern und Laden der Seite blockiert wird.

3. Der Ereignisauslöser-Thread

  • gehört zum Browser und nicht zur JS-Engine und wird zur Steuerung der Ereignisschleife verwendet (es ist verständlich, dass die JS-Engine selbst zu beschäftigt ist und den Browser zum Öffnen eines anderen Threads benötigt assist)
  • Wenn die JS-Engine einen Codeblock wie setTimeOut ausführen soll (er kann auch von anderen Threads im Browserkernel stammen, z. B. Mausklicks, asynchrone Ajax-Anforderungen usw.), wird die entsprechende Aufgabe ausgeführt zum Event-Thread hinzugefügt. Und ist für die Sortierung verantwortlich
  • Wenn das entsprechende Ereignis die Auslösebedingungen erfüllt und ausgelöst wird, fügt der Thread das Ereignis am Ende der ausstehenden Warteschlange hinzu und wartet auf die Verarbeitung durch die JS-Engine
  • Beachten Sie, dass aufgrund der Einzel- Thread-Beziehung von JS, diese ausstehenden Ereignisse Alle Ereignisse in der Warteschlange müssen in die Warteschlange gestellt werden, um auf die Verarbeitung durch die JS-Engine zu warten (sie werden ausgeführt, wenn die JS-Engine inaktiv ist)
  • Es kann einfach verstanden werden, dass es für die Verwaltung von a verantwortlich ist Reihe von Ereignissen und eine „Ereigniswarteschlange“. Nur Aufgaben in der Ereigniswarteschlange. Die JS-Engine wird ausgeführt, wenn sie inaktiv ist, und sie muss sie zur Ereigniswarteschlange hinzufügen, wenn ein Ereignis ausgelöst wird. Zum Beispiel Mausklick.

4. Timing-Trigger-Thread

  • Der Thread, in dem sich die legendären setInterval und setTimeout befinden
  • Der Browser-Timing-Zähler wird von der JavaScript-Engine nicht gezählt (da die JavaScript-Engine Single-Threaded ist, wenn sie blockiert ist).
  • Daher wird ein separater Thread zum Zeitpunkten und Auslösen des Timings verwendet. Nachdem das Timing abgeschlossen ist, wird es zur Ereigniswarteschlange hinzugefügt (entsprechend „wenn das Ereignis erfüllt ist“) Auslösende Bedingungen und wird ausgelöst“ des ereignisauslösenden Threads) und wartet darauf, dass die JS-Engine in den Leerlauf geht. implementieren.
  • Beachten Sie, dass W3C im HTML-Standard vorschreibt, dass das Zeitintervall unter 4 ms in setTimeout als 4 ms gezählt wird.

5. Asynchroner HTTP-Anfragethread

  • In XMLHttpRequest wird nach der Verbindung eine neue Threadanforderung über den Browser geöffnet
  • Wenn eine Statusänderung erkannt wird und eine Rückruffunktion festgelegt ist, generiert der asynchrone Thread einen Status Das Ereignis wird geändert. Dieser Rückruf wird dann in die Ereigniswarteschlange gestellt. Anschließend von der JavaScript-Engine ausgeführt.

Der Kommunikationsprozess zwischen dem Browser-Prozess und dem Browser-Kernel (Renderer-Prozess)

Dabei sollten Sie zunächst ein gewisses Verständnis für die Prozesse und Threads im Browser haben. Lassen Sie uns dann über den Browser des Browsers sprechen Sobald Sie verstanden haben, wie der Prozess (Steuerungsprozess) mit dem Kernel kommuniziert, können Sie diesen Teil des Wissens miteinander verbinden, um ein vollständiges Konzept von Anfang bis Ende zu erhalten.

Wenn Sie den Task-Manager öffnen und dann einen Browser öffnen, können Sie Folgendes sehen: Im Task-Manager werden zwei Prozesse angezeigt (einer ist der Hauptsteuerungsprozess und der andere ist der Renderprozess, der die Tab-Seite öffnet) und dann hier Schauen wir uns unter der Prämisse den gesamten Prozess an: (Es ist stark vereinfacht)

  • Wenn der Browserprozess eine Benutzeranforderung empfängt, muss er zunächst den Seiteninhalt abrufen (z. B. Ressourcen über das Netzwerk herunterladen) und Anschließend wird die Aufgabe über die RendererHost-Schnittstelle an Render (Kernel) übergeben. Der Renderer empfängt die Nachricht, erklärt sie kurz, übergibt sie an den Rendering-Thread und beginnt dann mit dem Rendern Die Anfrage lädt die Webseite und rendert die Webseite, was möglicherweise erfordert, dass der Browser-Prozess Ressourcen erhält und der GPU-Prozess beim Rendern hilft
  • Natürlich kann es JS-Threads für den Betrieb des DOM geben (dies kann zu Reflow und Neuzeichnen führen). )
Schließlich übergibt der Render-Prozess die Ergebnisse an den Browser-Prozess
  1. Der Browser-Prozess empfängt die Ergebnisse und zeichnet die Ergebnisse
  2. Hier ist ein einfaches Bild: (sehr vereinfacht)
Die Beziehung zwischen Threads im Browser-Kernel


Beherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständigAn diesem Punkt haben wir ein Gesamtkonzept für die Funktionsweise des Browsers. Als nächstes klären wir einfach einige Konzepte

Der GUI-Rendering-Thread und der JS-Engine-Thread schließen sich gegenseitig aus

Da JavaScript das DOM manipulieren kann, ändern Sie die Attribute dieser Elemente und rendern gleichzeitig die Schnittstelle (dh der JS-Thread und der UI-Thread werden ausgeführt). gleichzeitig), dann können die vor und nach dem Rendering-Thread erhaltenen Elementdaten inkonsistent sein.

Um unerwartete Rendering-Ergebnisse zu verhindern, stellt der Browser eine sich gegenseitig ausschließende Beziehung zwischen dem GUI-Rendering-Thread und der JS-Engine ein. Wenn die JS-Engine ausgeführt wird, wird der GUI-Thread angehalten und GUI-Updates werden gespeichert eine Warteschlange, bis der JS-Engine-Thread sofort ausgeführt wird, wenn er inaktiv ist.

JS blockiert das Laden der Seite

Aus der oben genannten sich gegenseitig ausschließenden Beziehung kann abgeleitet werden, dass JS die Seite blockiert, wenn die Ausführung zu lange dauert.

Angenommen, die JS-Engine führt beispielsweise eine große Menge an Berechnungen durch, selbst wenn die GUI zu diesem Zeitpunkt aktualisiert wird, werden sie in der Warteschlange gespeichert und warten auf die Ausführung, wenn die JS-Engine inaktiv ist. Dann wird die JS-Engine aufgrund der großen Menge an Berechnungen wahrscheinlich sehr lange im Leerlauf sein und sich natürlich extrem riesig anfühlen.

Versuchen Sie also zu vermeiden, dass die JS-Ausführung zu lange dauert, da dies zu einer inkonsistenten Darstellung der Seite führt und den Eindruck entstehen lässt, dass die Darstellung und das Laden der Seite blockiert sind.

Um dieses Problem zu lösen, zusätzlich zur Platzierung der Berechnung im Backend, wenn dies nicht vermieden werden kann und die große Berechnung mit der Benutzeroberfläche zusammenhängt, besteht meine Idee darin, setTimeout zu verwenden, um die Aufgabe aufzuteilen und etwas freie Zeit im Backend zu schaffen Mitte für die JS-Engine Gehen Sie und kümmern Sie sich um die Benutzeroberfläche, damit die Seite nicht direkt hängen bleibt.

Wenn Sie sich direkt für die mindestens erforderliche HTML5+-Version entscheiden, können Sie sich unten den WebWorker ansehen.

WebWorker, Multithreading von JS

Wie im vorherigen Artikel erwähnt, ist die JS-Engine Single-Threaded, und wenn die JS-Ausführungszeit zu lang ist, wird die Seite blockiert. Ist JS also wirklich machtlos? gegen CPU-intensive Berechnungen?

So wurden Web Worker später in HTML5 unterstützt. Die offizielle Erklärung von

MDN lautet:

Web Worker bietet eine einfache Möglichkeit, Skripte in einem Hintergrundthread für Webinhalte auszuführen.

Threads können Aufgaben ausführen, ohne die Benutzeroberfläche zu beeinträchtigen. Ein Worker ist ein Objekt, das mit einem Konstruktor (Worker()) erstellt wurde, der eine benannte JavaScript-Datei ausführt (diese Datei enthält den Code, der im Worker-Thread ausgeführt wird).

Worker werden in einem anderen globalen Kontext ausgeführt, der sich vom aktuellen Fenster unterscheidet.

Daher führt die Verwendung der Fensterverknüpfung zum Abrufen des aktuellen globalen Bereichs (anstelle von „self“) zu einem Fehler innerhalb eines Workers Untergeordneter Thread (untergeordneter Thread) Wird vom Browser geöffnet, vollständig vom Hauptthread gesteuert und kann das DOM nicht bedienen)

Der JS-Engine-Thread und der Arbeitsthread kommunizieren auf bestimmte Weise (PostMessage-API, für deren Interaktion Serialisierungsobjekte erforderlich sind).

Wenn es also sehr zeitaufwändige Arbeiten gibt, öffnen Sie bitte einen separaten Worker-Thread, damit der Hauptthread der JS-Engine nicht beeinträchtigt wird, egal wie weltbewegend er ist Das Ergebnis muss berechnet und dem Hauptthread mitgeteilt werden.

Und beachten Sie, dass die JS-Engine im Wesentlichen nicht als vom Browser geöffnetes Plug-in verstanden werden kann für die JS-Engine, die speziell zur Lösung umfangreicher Rechenprobleme verwendet wird.

Andere: Die detaillierte Erklärung von Worker würde den Rahmen dieses Artikels sprengen, daher werde ich nicht auf Details eingehen. WebWorker und SharedWorker ) Kernel-Prozess)-Freigabe

Chrome erstellt also einen neuen Thread im Render-Prozess (jede Tab-Seite ist ein Render-Prozess), um das JavaScript-Programm im Worker auszuführen.

SharedWorker wird von allen Seiten des Browsers gemeinsam genutzt und kann nicht auf die gleiche Weise wie Worker implementiert werden, da es nicht mit einem Render-Prozess verbunden ist und von mehreren Render-Prozessen gemeinsam genutzt werden kann.

Daher erstellt der Chrome-Browser einen separaten Prozess für SharedWorker Für JavaScript-Programme gibt es nur einen SharedWorker-Prozess für jedes gleiche JavaScript im Browser, unabhängig davon, wie oft es erstellt wird.

Wenn man das sieht, sollte es leicht verständlich sein, dass es sich um den Unterschied zwischen Prozessen und Threads handelt. SharedWorker wird von einem unabhängigen Prozess verwaltet, und WebWorker ist nur ein Thread unter dem Render-Prozess.

Browser-Rendering-ProzessErgänzender Browser-Rendering-Prozess (einfache Version)

Um das Verständnis zu vereinfachen, wird die Vorarbeit direkt weggelassen:

Der Browser gibt die URL ein, der Browser-Hauptprozess übernimmt, öffnet einen Download-Thread, und dann Stellen Sie eine http-Anfrage (ohne DNS-Abfrage, IP-Adressierung usw.), warten Sie dann auf die Antwort, rufen Sie den Inhalt ab und übertragen Sie den Inhalt dann über die RendererHost-Schnittstelle an den Renderer-Prozess
Der Browser-Rendering-Prozess beginnt
Nach dem Der Browserkernel erhält den Inhalt. Das Rendern kann grob in die folgenden Schritte unterteilt werden:

HTML analysieren und DOM-Baum erstellen
CSS analysieren und Renderbaum erstellen (CSS-Code in eine baumförmige Datenstruktur analysieren und dann mit DOM kombinieren, um ihn in den Renderbaum zusammenzuführen)
Layout des Renderbaums (Layout/Reflow), verantwortlich für die Berechnung die Größe und Position jedes Elements
Zeichnen Sie den Renderbaum (Farbe) und zeichnen Sie die Seitenpixelinformationen
Der Browser sendet die Informationen jeder Ebene an die GPU, und die GPU setzt jede Ebene zusammen und zeigt sie auf dem Bildschirm an.
Alle detaillierten Schritte wurden weggelassen. Nach Abschluss des Renderns erfolgt das Ladeereignis und dann Ihre eigene JS-Logikverarbeitung.

Da einige detaillierte Schritte weggelassen wurden, möchte ich einige Details erwähnen, die möglicherweise Aufmerksamkeit erfordern.
Beherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständig

Die Reihenfolge des Ladeereignisses und des DOMContentLoaded-Ereignisses

Wie oben erwähnt, wird das Ladeereignis ausgelöst, nachdem das Rendern abgeschlossen ist. Können Sie also die Reihenfolge des Ladeereignisses und des DOMContentLoaded-Ereignisses unterscheiden?

Es ist ganz einfach, kennen Sie einfach ihre Definitionen:

Wenn das DOMContentLoaded-Ereignis ausgelöst wird, nur, wenn das DOM geladen ist, ausgenommen Stylesheets, Bilder, asynchrone Skripte usw.
Wenn das Onload-Ereignis ausgelöst wird, wurden alle DOM, Stylesheets, Skripte und Bilder auf der Seite geladen, das heißt, sie wurden gerendert. Die Reihenfolge lautet also: DOMContentLoaded ->Will CSS-Ladeblock der Dom-Baum? Rendering

Wovon wir hier sprechen, ist die Einführung von CSS im Header

Zuallererst wissen wir alle, dass CSS von einem separaten Download-Thread asynchron heruntergeladen wird.

Dann lassen Sie uns über die folgenden Phänomene sprechen:

Das Laden von CSS blockiert nicht das Parsen des DOM-Baums (DOM wird beim asynchronen Laden wie gewohnt erstellt)

    aber es blockiert das Rendern des Renderbaums (Sie müssen warten, bis CSS geladen ist beim Rendern, da der Renderbaum CSS-Informationen benötigt)
  • Dies kann auch ein Optimierungsmechanismus des Browsers sein. Denn wenn Sie CSS laden, können Sie den Stil des DOM-Knotens unten ändern. Wenn das CSS-Laden das Rendern des Renderbaums nicht blockiert, muss der Renderbaum nach dem Laden des CSS möglicherweise neu gezeichnet oder umgeformt werden verursacht einige Probleme. Es gibt keinen notwendigen Verlust.
Parsen Sie also einfach zuerst die Struktur des DOM-Baums, schließen Sie die Arbeit ab, die ausgeführt werden kann, und warten Sie dann, bis Ihr CSS geladen ist, und rendern Sie dann den Renderbaum entsprechend dem endgültigen Stil hinsichtlich der Leistung ein wenig.

Normale Schichten und Verbundschichten

Das Konzept des Verbundstoffs wird im Rendering-Schritt erwähnt.

Es kann einfach so verstanden werden. Die vom Browser gerenderten Ebenen umfassen im Allgemeinen zwei Kategorien: normale Ebenen und zusammengesetzte Ebenen.

Zunächst kann der normale Dokumentfluss als zusammengesetzte Ebene verstanden werden (hier als Standard-Zusammensetzung bezeichnet). Ebene, egal wie viele Elemente darin hinzugefügt werden, sie befinden sich tatsächlich in derselben zusammengesetzten Ebene)

Zweitens gehört das absolute Layout (dasselbe gilt für das feste) dazu, obwohl es vom normalen Dokumentenfluss getrennt werden kann zur Standard-Verbundschicht.

Anschließend können Sie über die Hardwarebeschleunigung eine neue Verbundschicht deklarieren, die Ressourcen separat zuweist (natürlich wird sie auch vom normalen Dokumentenfluss getrennt, sodass sie unabhängig von der Änderung der Verbundschicht nicht beeinträchtigt wird). . Reflow-Neuzeichnung in der Standard-Verbundebene)

Es ist leicht zu verstehen: In der GPU wird jede Verbundebene separat gezeichnet, sodass sie sich nicht gegenseitig beeinflusst. Aus diesem Grund ist der Hardwarebeschleunigungseffekt einiger Szenen erstklassig

Kann Chrome DevTools sein –> Weitere Tools –> Rendering –>

Verwandeln Sie dieses Element in ein zusammengesetztes Bild. Die Ebene ist die legendäre Hardwarebeschleunigungstechnologie.

Die am häufigsten verwendeten Methoden: Translate3D, TranslateZ
Deckkraftattribut/Übergangsanimation (die Verbundebene wird nur während der Ausführung der Animation erstellt und die Das Element kehrt in den vorherigen Zustand zurück, nachdem die Animation nicht gestartet oder beendet wurde.
Will-Chang-Attribut (dies ist relativ entfernt), wird im Allgemeinen in Verbindung mit Opazität und Übersetzung verwendet. Seine Funktion besteht darin, dem Browser im Voraus mitzuteilen, dass sich der Status ändert erstellt werden sollen, damit der Browser einige Optimierungsarbeiten durchführt (am besten geben Sie diese nach der Verwendung frei)

Elemente wie Video, Iframe, Canvas, Webgl usw.

Andere, wie z. B. das bisherige Flash-Plug- im

Der Unterschied zwischen Absolut- und Hardwarebeschleunigung

Es ist ersichtlich, dass Absolut zwar vom normalen Dokumentenfluss, jedoch nicht von der Standard-Verbundschicht getrennt werden kann. Selbst wenn sich die absoluten Informationen ändern, ändert sich dadurch der Renderbaum im normalen Dokumentfluss nicht. Wenn der Browser jedoch schließlich zeichnet, wird die gesamte zusammengesetzte Ebene gezeichnet, sodass sich Änderungen der absoluten Informationen weiterhin auf die Zeichnung auswirken der gesamten Verbundschicht. (Der Browser zeichnet es neu. Wenn die zusammengesetzte Ebene viel Inhalt enthält, ändern sich die von absolute eingebrachten Zeichnungsinformationen zu stark und der Ressourcenverbrauch ist sehr hoch.)

Und die Hardwarebeschleunigung erfolgt direkt in einer anderen zusammengesetzten Ebene Ebene (beginnend bei Null), daher wirken sich ihre Informationsänderungen nicht auf die Standard-Verbundebene aus (natürlich wirkt sie sich definitiv intern auf ihre eigene Verbundebene aus), sondern lösen nur die endgültige Komposition (Ausgabeansicht) aus

Die Rolle zusammengesetzter Ebenen

Im Allgemeinen wird ein Element nach der Aktivierung der Hardwarebeschleunigung zu einer zusammengesetzten Ebene, die vom normalen Dokumentfluss unabhängig sein kann. Nach der Änderung kann das Neuzeichnen der gesamten Seite vermieden und die Leistung verbessert werden Versuchen Sie, keine großen Mengen zusammengesetzter Ebenen zu verwenden, da die Seite sonst aufgrund des übermäßigen Ressourcenverbrauchs verzögert wird Standardmäßig wird die zusammengesetzte Ebene für nachfolgende Elemente gerendert das gleiche und haben die gleichen relativen oder absoluten Attribute), es wird standardmäßig zu einem zusammengesetzten Layer-Rendering. Wenn es nicht richtig gehandhabt wird, wird es die Leistung stark beeinträchtigen. Um es einfach zu verstehen, kann es tatsächlich als implizit betrachtet werden Synthesekonzept: Wenn a eine zusammengesetzte Schicht ist und b über a liegt, wird b auch implizit in eine zusammengesetzte Schicht umgewandelt, was besondere Aufmerksamkeit erfordert.

Sprechen Sie über den Betriebsmechanismus von JS von EventLoop

Bis zu diesem Punkt erfolgt bereits nach dem ersten Rendern der Browserseite eine Analyse des Betriebsmechanismus der JS-Engine.

Beachten Sie, dass wir nicht über Konzepte wie ausführbaren Kontext, VO, Scop-Kette usw. sprechen (diese können in einem anderen Artikel organisiert werden). Hier sprechen wir hauptsächlich darüber, wie JS-Code in Verbindung mit Event Loop ausgeführt wird.

Voraussetzung für das Lesen dieses Teils ist, dass Sie bereits wissen, dass die JS-Engine Single-Threaded ist und mehrere der oben genannten Konzepte hier verwendet werden:

JS-Engine-Thread

Event-Trigger-Thread

Timed-Trigger-Thread

Dann Ein Konzept verstehen:

  • JS ist in synchrone Aufgaben und asynchrone Aufgaben unterteilt.
  • Synchronisierte Aufgaben werden im Hauptthread ausgeführt und bilden einen Ausführungsstapel.

  • Neben dem Hauptthread verwaltet der ereignisauslösende Thread eine Aufgabenwarteschlange, solange Da die asynchrone Aufgabe ausgeführt wurde, platzieren Sie ein Ereignis in der Aufgabenwarteschlange, um die laufenden Ergebnisse zu erfahren.
Sobald alle synchronen Aufgaben im Ausführungsstapel ausgeführt wurden (die JS-Engine ist zu diesem Zeitpunkt inaktiv), liest das System die Aufgabenwarteschlange, fügt ausführbare asynchrone Aufgaben zum ausführbaren Stapel hinzu und startet die Ausführung.

    Sehen Sie sich das Bild an:
  • Wenn Sie das sehen, sollten Sie verstehen: Warum werden von setTimeout gepushte Ereignisse manchmal nicht rechtzeitig ausgeführt? Da der Hauptthread möglicherweise noch nicht inaktiv ist und anderen Code ausführt, wenn er in die Ereignisliste verschoben wird, treten natürlich Fehler auf.
  • Der Ereignisschleifenmechanismus wird weiter ergänzt


Beherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständig

Das obige Bild beschreibt grob:

Der Hauptthread generiert einen Ausführungsstapel, wenn er ausgeführt wird. Wenn der Code im Stapel bestimmte APIs aufruft, werden verschiedene hinzugefügt Ereignisse in die Ereigniswarteschlange (Wenn die Auslöserbedingungen erfüllt sind, z. B. die Ajax-Anfrage abgeschlossen ist)

Nachdem der Code im Stapel ausgeführt wurde, werden die Ereignisse in der Ereigniswarteschlange gelesen, um diese Rückrufe auszuführen, und so weiterBeherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständig

Beachten Sie, dass Sie immer auf den Stapel warten müssen. Die Ereignisse in der Ereigniswarteschlange werden erst gelesen, nachdem der Code ausgeführt wurde.

    Lassen Sie uns separat über den Timer sprechen.
  • Der Kern des oben genannten Ereignisschleifenmechanismus ist: JS-Engine-Thread und Ereignisauslöser-Thread
  • Aber es gibt einige versteckte Dinge in den Ereignisdetails, z. B. wie man eine bestimmte Zeit abwartet, bevor man es nach dem Aufruf von setTimeout zur Ereigniswarteschlange hinzufügt?

Wird es von der JS-Engine erkannt? Natürlich nicht. Es wird vom Timer-Thread gesteuert (da die JS-Engine selbst zu beschäftigt ist und keine Zeit hat, etwas anderes zu tun)

Warum brauchen wir einen separaten Timer-Thread? Da es sich bei der JavaScript-Engine um eine Single-Thread-Engine handelt, wirkt sich dies auf die Genauigkeit des Timings aus, wenn sie sich in einem blockierten Thread-Zustand befindet. Daher ist es erforderlich, einen separaten Thread für das Timing zu öffnen.

Wann wird der Timer-Thread verwendet? Bei Verwendung von setTimeout oder setInterval muss der Timer-Thread eine Zeitmessung durchführen. Nach Ablauf der Zeit wird das spezifische Ereignis in die Ereigniswarteschlange verschoben.

setTimeout anstelle von setInterval

Es gibt einen Unterschied zwischen der Verwendung von setTimeout zum Simulieren eines regulären Timings und der direkten Verwendung von setInterval.

Denn jedes Mal, wenn setTimeout ausgeführt wird, wird setTimeout nach einer gewissen Zeit ausgeführt. In der Mitte treten Fehler auf (der Fehler hängt mit der Codeausführungszeit zusammen)

Und setInterval ist Jedes Mal, wenn ein Ereignis für einen bestimmten Zeitraum verschoben wird, ist die tatsächliche Ausführungszeit möglicherweise nicht genau. Es ist auch möglich, dass das nächste Ereignis eintritt, bevor das Ereignis abgeschlossen ist.

Und setInterval hat einige fatale Probleme:

Kumulativer Effekt: Wenn die Ausführung des setInterval-Codes vor dem erneuten Hinzufügen zur Warteschlange noch nicht abgeschlossen ist, führt dies dazu, dass der Timer-Code mehrmals hintereinander ohne Intervall ausgeführt wird. Selbst wenn sie in normalen Intervallen ausgeführt werden, kann die Ausführungszeit mehrerer setInterval-Codes kürzer als erwartet sein (da die Codeausführung eine gewisse Zeit in Anspruch nimmt. Browser wie iOS Webview oder Safari verfügen beispielsweise über eine Funktion, die dies nicht tut). Beim Scrollen ausführen. Wenn Sie setInterval verwenden, werden Sie feststellen, dass es nach Abschluss des Scrollens mehrmals ausgeführt wird. Da beim Scrollen der JS-Akkumulationsrückruf nicht ausgeführt wird, wird dies der Fall sein Dies führt zu festgefahrenen Problemen und einigen unbekannten Fehlern im Container (dieser Abschnitt wird später hinzugefügt, da setInterval über eine eigene Optimierung verfügt und keine wiederholten Rückrufe hinzufügt).
Und wenn der Browser minimiert und angezeigt wird, führt setInterval das Programm nicht aus stellt die Rückruffunktion von setInterval in die Warteschlange und wartet darauf, dass der Browser beim erneuten Öffnen des Fensters alles sofort ausführt
Angesichts so vieler Probleme wird derzeit allgemein davon ausgegangen, dass die beste Lösung ist: Verwenden Sie setTimeout, um setInterval zu simulieren, oder verwenden Sie bei besonderen Anlässen direkt requestAnimationFrame.

Zusatz: Wie in der JS-Erhöhung erwähnt, wird die JS-Engine SetInterval optimiert. Wenn sich in der aktuellen Ereigniswarteschlange ein setInterval-Rückruf befindet, wird dieser nicht wiederholt hinzugefügt.

Erweiterte Ereignisschleife: Makrotask und Mikrotask

Der JS-Ereignisschleifenmechanismus wurde oben in ES5 geklärt, aber jetzt, da ES6 weit verbreitet ist, werden Sie immer noch auf einige Probleme stoßen folgende Frage:

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
    console.log('promise1');
}).then(function() {
    console.log('promise2');
});

console.log('script end');
Hmm, die richtige Ausführungsreihenfolge ist so:

script start
script end
promise1
promise2
setTimeout
Warum? Weil es in Promise ein neues Konzept gibt: Mikrotask

Oder JS ist in zwei Aufgabentypen unterteilt: Makrotask und Mikrotask. In ECMAScript wird Mikrotask als Job bezeichnet, und Makrotask kann als Task bezeichnet werden.

Was sind ihre Definitionen? der Unterschied? Vereinfacht ausgedrückt kann es wie folgt verstanden werden:

1. Makroaufgabe (auch Makroaufgabe genannt)
Es kann verstanden werden, dass der vom Ausführungsstapel jedes Mal ausgeführte Code eine Makroaufgabe ist (einschließlich jedes Mal, wenn ein Ereignisrückruf erfolgt). wird aus der Ereigniswarteschlange abgerufen und zur Ausführung im Ausführungsstapel abgelegt)

    Jede Aufgabe führt diese Aufgabe von Anfang bis Ende aus und führt keine anderen Aufgaben aus
  • Um die Ausführung von JS-internen Aufgaben und DOM-Aufgaben zu ermöglichen Auf geordnete Weise führt der Browser es in einer Aufgabe aus. Rendern Sie die Seite nach dem Ende erneut, bevor die nächste Aufgabenausführung beginnt (Aufgabe -> Rendering-> Aufgabe -> ...)
2. Unter Mikrotask (auch Mikrotask genannt)
können Aufgaben verstanden werden, die unmittelbar nach der Ausführung der aktuellen Aufgabe ausgeführt werden

    Das heißt, nach der aktuellen Aufgabe, vor der nächsten Aufgabe und vor dem Rendern
  • Daher ist seine Reaktionsgeschwindigkeit schneller als bei setTimeout (setTimeout ist eine Aufgabe), da nicht auf das Rendern gewartet werden muss
  • Mit anderen Worten: Nachdem eine bestimmte Makrotask ausgeführt wurde, werden alle während ihrer Ausführung generierten Mikrotasks ausgeführt (vor dem Rendern). )
3. Welche Art von Szenario bildet eine Makrotask? Makrotask: Hauptcodeblock, setTimeout, setInterval usw. (jedes Ereignis in der Ereigniswarteschlange ist eine Makrotask) .nextTick usw.
    Zusätzlich: In der Knotenumgebung hat der Prozess .nextTick eine höhere Priorität als Promise, was einfach so verstanden werden kann: Nach dem Ende der Makroaufgabe wird zuerst der nextTickQueue-Teil der Mikrotask-Warteschlange ausgeführt. und dann wird der Promise-Teil der Mikrotask ausgeführt.
  • Lassen Sie es uns anhand von Threads verstehen:
  • (1) Ereignisse in der Makrotask werden in einer Ereigniswarteschlange platziert, und diese Warteschlange wird vom ereignisauslösenden Thread verwaltet.

(2) Alle Mikrotasks in der Mikrotask werden hinzugefügt. Gehen Sie zur Mikrotask-Warteschlange (Jobwarteschlangen) und warten Sie, bis die aktuelle Makroaufgabe ausgeführt wird, nachdem sie ausgeführt wurde. Diese Warteschlange wird vom JS-Engine-Thread verwaltet (dies basiert auf meinem eigenen Verständnis und meinen eigenen Spekulationen, da sie nahtlos unter dem Hauptthread ausgeführt wird)

Also , um den Betriebsmechanismus zusammenzufassen:

Führen Sie eine Makroaufgabe aus (wenn sie sich nicht auf dem Stapel befindet, holen Sie sie aus der Ereigniswarteschlange).

Wenn während der Ausführung eine Mikroaufgabe auftritt, fügen Sie sie der Aufgabenwarteschlange der Mikroaufgabe hinzu

Makro Nachdem die Aufgabe ausgeführt wurde, werden alle Mikrotasks in der aktuellen Mikrotask-Warteschlange sofort ausgeführt (nacheinander ausgeführt).
  • Nachdem die aktuelle Makroaufgabe ausgeführt wurde, beginnt die Überprüfung des Renderings, und dann übernimmt der GUI-Thread das Rendering
  • Nachdem das Rendern abgeschlossen ist, übernimmt der JS-Thread weiterhin und startet eine Makroaufgabe (aus der Ereigniswarteschlange erhalten)
  • Wie in der Abbildung gezeigt:
  • Achten Sie außerdem auf den Unterschied zwischen Versprechen Sie Polyfill und die offizielle Version:

In der offiziellen Version handelt es sich um eine Standard-Mikrotask-Form
Polyfill, im Allgemeinen Es wird durch setTimeout simuliert, liegt also in Form einer Makrotask vorBeherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständig Beachten Sie, dass einige Browser unterschiedliche Ausführungsergebnisse haben (weil sie kann Mikrotask als Makrotask ausführen), aber der Einfachheit halber werden die Szenarien unter einigen nicht standardmäßigen Browsern hier nicht beschrieben (aber denken Sie daran, dass einige Browser möglicherweise nicht standardmäßig sind)

补充:使用MutationObserver实现microtask

MutationObserver可以用来实现microtask (它属于microtask,优先级小于Promise, 一般是Promise不支持时才会这样做)

它是HTML5中的新特性,作用是:监听一个DOM变动, 当DOM对象树发生任何变动时,Mutation Observer会得到通知

像以前的Vue源码中就是利用它来模拟nextTick的, 具体原理是,创建一个TextNode并监听内容变化, 然后要nextTick的时候去改一下这个节点的文本内容, 如下:

var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))

observer.observe(textNode, {
    characterData: true
})
timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
}

不过,现在的Vue(2.5+)的nextTick实现移除了MutationObserver的方式(据说是兼容性原因), 取而代之的是使用MessageChannel (当然,默认情况仍然是Promise,不支持才兼容的)。

MessageChannel属于宏任务,优先级是:MessageChannel->setTimeout, 所以Vue(2.5+)内部的nextTick与2.4及之前的实现是不一样的,需要注意下。

【相关推荐:javascript视频教程web前端

Das obige ist der detaillierte Inhalt vonBeherrschen Sie den Funktionsmechanismus und die Prinzipien von JavaScript vollständig. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen