Heim  >  Artikel  >  Web-Frontend  >  Vertiefendes Verständnis der zugrunde liegenden JS-Mechanismen wie JS-Datentypen, Vorkompilierung und Ausführungskontext

Vertiefendes Verständnis der zugrunde liegenden JS-Mechanismen wie JS-Datentypen, Vorkompilierung und Ausführungskontext

WBOY
WBOYnach vorne
2021-12-27 18:48:522385Durchsuche

JavaScript besteht aus drei Teilen: dem Document Object Model DOM, dem Browser Object Model BOM und seinem Kern-ECMAScript. Dieser Artikel vermittelt Kenntnisse über die zugrunde liegenden Prinzipien von JavaScript und hofft, für alle hilfreich zu sein.

Vertiefendes Verständnis der zugrunde liegenden JS-Mechanismen wie JS-Datentypen, Vorkompilierung und Ausführungskontext

JavaScript ist eine wörtlich interpretierte Skriptsprache, die dynamisch, schwach typisiert und prototypbasiert ist. JavaScript ist in dem von uns verwendeten Webbrowser verankert und sein Interpreter ist die JavaScript-Engine im Browser. Diese auf der Client-Seite weit verbreitete Skriptsprache wurde zunächst zur Abwicklung einiger Eingabevalidierungsvorgänge verwendet, die zuvor von serverseitigen Sprachen durchgeführt wurden. Mit der Entwicklung des Web-Zeitalters ist JavaScript weiter gewachsen und hat sich zu einer voll funktionsfähigen Programmiersprache entwickelt . Seine Verwendung beschränkt sich nicht mehr auf die einfache Datenvalidierung, sondern bietet die Möglichkeit, mit fast allen Aspekten des Browserfensters und seines Inhalts zu interagieren. Es ist sowohl eine sehr einfache als auch eine äußerst komplexe Sprache. Wenn wir JavaScript wirklich beherrschen wollen, müssen wir einige der zugrunde liegenden Designprinzipien tiefgreifend verstehen. Dieser Artikel bezieht sich auf die Buchreihen „JavaScript Advanced Programming“ und „JS You Don't Know“, um einige grundlegende Kenntnisse über JavaScript zu erläutern.

Datentypen

JavaScript-Datentypen können je nach Speichermethode in zwei Typen unterteilt werden: primitive Datentypen (Originalwerte) und Referenzdatentypen (Referenzwerte).

Derzeit gibt es sechs primitive Datentypen, darunter „Number“, „String“, „Boolean“, „Null“, „Undefiniert“ und „Symbol“ (ES6). Diese Typen sind tatsächliche Werte, die in Variablen gespeichert sind, die direkt manipuliert werden können. Primitive Datentypen werden auf dem Stapel gespeichert und die Datengröße wird direkt nach Wert bestimmt, sodass auf sie direkt nach Wert zugegriffen werden kann.

 Der Referenzdatentyp ist Object. In JavaScript sind alle außer primitiven Datentypen Objekttypen, einschließlich Arrays, Funktionen, reguläre Ausdrücke usw., die alle Objekte sind. Ein Referenztyp ist ein im Heap-Speicher gespeichertes Objekt, und eine Variable ist eine im Stapelspeicher gespeicherte Referenzadresse, die auf ein Objekt im Heap-Speicher verweist. Wenn eine Variable definiert und auf einen Referenzwert initialisiert wird und sie einer anderen Variablen zugewiesen wird, speichern die beiden Variablen dieselbe Adresse und verweisen auf denselben Speicherplatz im Heap-Speicher. Wenn Sie den Wert eines Referenzdatentyps über eine der Variablen ändern, ändert sich auch die andere Variable entsprechend. , Für den ursprünglichen Datentyp ist NULL etwas Besonderes

(NULL wird als leere Objektreferenz betrachtet) , andere können Typeof verwenden, um eine genaue Beurteilung zu treffen:

Ausdrucksformel

Rückgabewert

Typ von 123

'Nummer'

Typ von „abc“

'string'

typeof true

'boolean'

typeof null

'object'

typeof undefiniert

'undefiniert '

Typ der unbekannten Variablen (

undefinierte Variable)

'undefiniert'

Typ des Symbols()

'symbol'

typeof function() {}

'function'

typeof {}

'Objekt'

Typ von []

'Objekt'

Typ von (/[0-9,a-z]/)

'Objekt'

Für Nulltypen können Sie den Kongruenzoperator zur Beurteilung verwenden. Einem deklarierten, aber nicht initialisierten Variablenwert wird standardmäßig undefiniert zugewiesen ( kann auch manuell undefiniert zugewiesen werden) In JavaScript kann der Gleichheitsoperator == nicht zwischen null und undefiniert unterscheiden Ihr Gleichheitstest sollte „true“ zurückgeben. Um zwei Werte genau zu unterscheiden, müssen Sie den Kongruenzoperator === verwenden.

​​​​​​Für Referenzdatentypen, mit Ausnahme der Funktion, die über ein spezielles Methodendesign verfügt und mit typeof genau beurteilt werden kann, geben alle anderen den Objekttyp zurück. Wir können „instanceof“ verwenden, um Referenztypwerte zu beurteilen. Instanz von erkennt, ob ein Objekt A eine Instanz eines anderen Objekts B ist. Auf der untersten Ebene wird überprüft, ob Objekt B in der Prototypenkette von Objekt A vorhanden ist der Artikel) . Gibt true zurück, wenn vorhanden, false, wenn nicht vorhanden.
Ausdruck

Rückgabewert

[1,2,3] Instanz von Array

'true'

Funktion foo(){ } Instanz der Funktion

'true'

/[0-9,a-z]/ Instanz von RegExp

'wahr'

neue Date()-Instanz von Date

'true'

{Name:"Alan",Alter:"22"} Instanz von Object

'true'

Da alle Referenztypwerte Instanzen von Object sind, verwenden Sie den Instanzoperator, um sie als Object zu beurteilen, und das Ergebnis gibt ebenfalls true zurück.

Funktion foo(){ } Instanz von Object/[0-9,a-z]/ Instanz von Objectneue Date()-Instanz des Objekts

Ausdruck: true'

'true'

'true'

'true'

Natürlich gibt es eine leistungsfähigere Methode, die jeden Datentyp in jedem JavaScript genau bestimmen kann, und das ist die Methode Object.prototype.toString.call(). In ES5 verfügen alle Objekte (native Objekte und Hostobjekte) über eine interne Eigenschaft [[Klasse]], deren Wert eine Zeichenfolge ist, die den Typ des Objekts aufzeichnet. Enthält derzeit „Array“, „Boolean“, „Date“, „Error“, „Function“, „Math“, „Number“, „Object“, „RegExp“, „String“, „Arguments“, „JSON“, "Symbol". Diese interne Eigenschaft kann über die Methode Object.prototype.toString() angezeigt werden, es gibt keine andere Möglichkeit.

Wenn die Methode Object.prototype.toString() aufgerufen wird, werden die folgenden Schritte ausgeführt: 1. Den Attributwert [[Klasse]] dieses Objekts abrufen(Ich werde über dieses Objekt sprechen später im Artikel) . 2. Platzieren Sie den Wert zwischen den beiden Zeichenfolgen „[object“ und „]“ und verketten Sie sie. 3. Geben Sie die gespleißte Zeichenfolge zurück.

Wenn der Wert null ist, gibt die Methode Object.prototype.toString() direkt „[object Null]“ zurück. Wenn der Wert von this undefiniert ist, wird „[object Undefiniert]“ direkt zurückgegeben.

Ausdruck

Rückgabewert

Object.prototype.toString.call(123)

[Objektnummer]

Object.prototype.toString.call("abc")

[object String]

Object.prototype.toString.call(true)

[object. Boolean ]

Object.prototype.toString.call(null)

[object. Null]

Object.prototype.toString.call(undefiniert )

[Objekt undefiniert]

Object.prototype.toString.call(Symbol())

[Objektsymbol]

Object.prototype.toString . call(function foo(){})

[object Function]

Object.prototype.toString.call([1,2,3])

[ object Array]

Object.prototype.toString.call({name:"Alan" })

[object Object]

Object.pro Typ. toString.call (neues Datum ())

[Objektdatum]

Object.prototype.toString.call (regexp ())

[Objekt Regexp]

Object.prototype.toString.call(window.JSON)

[Objekt JSON]

Object.prototype.toString.call(Math)

[Objekt Math]


Die Methode call() kann den Punkt ändern, wenn die Methode Object.prototype.toString() aufgerufen wird, sodass sie auf das Objekt zeigt, das wir übergeben, sodass wir das Attribut [[Class]] des Objekts erhalten können, das wir übergeben übergeben( Der gleiche Effekt kann mit Object.prototype.toString.apply() erzielt werden.

  JavaScript-Datentypen können auch konvertiert werden. Die Datentypkonvertierung ist in zwei Methoden unterteilt: explizite Typkonvertierung und implizite Typkonvertierung.

    Zu den Methoden, die für die Anzeigetypkonvertierung aufgerufen werden können, gehören Boolean(), String(), Number(), parseInt(), parseFloat() und toString() (null und undefinierte Werte ​​haben diese Methode nicht) , ihre jeweiligen Verwendungszwecke sind auf einen Blick klar, daher werde ich sie hier nicht einzeln vorstellen.

Da JavaScript eine schwach typisierte Sprache ist, können die Datentypen auf beiden Seiten des Operators bei Verwendung von arithmetischen Operatoren beliebig sein. Es ist nicht erforderlich, denselben Typ wie bei Java oder der C-Sprache anzugeben, die Engine wird dies tun automatisch implizit Typkonvertierung. Die implizite Typkonvertierung ist nicht so intuitiv wie die explizite Typkonvertierung. Es gibt drei Hauptkonvertierungsmethoden:
1. Konvertieren Sie den Wert in einen primitiven Wert: toPrimitive()

2. Konvertieren Sie den Wert in eine Zahl: toNumber()

3 . Konvertieren Sie den Wert in eine Zahl. Konvertieren Sie den Wert in eine Zeichenfolge: toString()

Im Allgemeinen werden die Zahlen beim Hinzufügen von Zahlen und Zeichenfolgen in Zeichenfolgen umgewandelt, wenn eine Wahrheitswertbeurteilung durchgeführt wird (z. B. if, ||, &&). Der Parameter wird in einen booleschen Wert umgewandelt; wenn Vergleichsoperationen, arithmetische Operationen oder automatische Inkrementierungs- und Dekrementierungsoperationen ausgeführt werden, werden die Parameter in Zahlenwerte umgewandelt, wenn das Objekt eine implizite Typkonvertierung erfordert, die toString()-Methode des Objekts oder Der Rückgabewert der valueOf()-Methode.

Über NaN:

NaN ist ein spezieller numerischer Wert, der einen nicht numerischen Wert darstellt. Erstens gibt jede arithmetische Operation, an der NaN beteiligt ist, NaN zurück. Zweitens ist NaN keinem Wert gleich, auch nicht NaN selbst. ECMAScript definiert eine isNaN()-Funktion, mit der getestet werden kann, ob ein Parameter „nicht numerisch“ ist. Zunächst wird versucht, das Argument implizit in einen numerischen Wert umzuwandeln, wobei „true“ zurückgegeben wird, wenn es nicht in einen numerischen Wert umgewandelt werden kann.


Wir können zuerst typeof verwenden, um zu bestimmen, ob es sich um den Zahlentyp handelt, und dann isNaN verwenden, um zu bestimmen, ob die aktuellen Daten NaN sind.

Über Strings:

Strings in JavaScript sind unveränderlich. Sobald Strings erstellt wurden, können ihre Werte nicht geändert werden. Um die von einer Variablen enthaltene Zeichenfolge zu ändern, zerstören Sie zunächst die ursprüngliche Zeichenfolge und füllen Sie die Variable dann mit einer anderen Zeichenfolge, die den neuen Wert enthält. Dieser Vorgang läuft im Hintergrund ab und ist der Grund dafür, dass einige ältere Browser beim Verketten von Zeichenfolgen sehr langsam sind.

Um die Bedienung grundlegender Typwerte zu erleichtern, bietet ECMAScript außerdem drei spezielle Referenztypen: Boolean, Number und String. Primitive Datentypen haben keine Eigenschaften und Methoden, um sie zu lesen. Der Zugriffsprozess befindet sich im Lesemodus und im Hintergrund wird ein entsprechendes primitives Wrapper-Objekt erstellt, das uns dies ermöglicht Rufen Sie einige Methoden auf, um diese Daten zu bearbeiten. Dieser Prozess ist in drei Schritte unterteilt: 1. Erstellen Sie eine Instanz des ursprünglichen Verpackungstyps. 2. Rufen Sie die angegebene Methode für die Instanz auf. 3. Zerstören Sie die Instanz.
Der Hauptunterschied zwischen Referenztypen und primitiven Wrapper-Typen besteht im Lebenszyklus des Objekts. Das automatisch erstellte primitive Wrapper-Typ-Objekt existiert nur zum Zeitpunkt der Ausführung einer Codezeile und wird dann sofort zerstört, sodass wir keine Attribute hinzufügen können zu primitiven Typwerten zur Laufzeit und Methoden.

Vorkompilierung

In dem Buch „JavaScript You Don't Know“ erklärte der Autor, dass JavaScript zwar als „dynamische Sprache“ oder „interpretierte Ausführungssprache“ klassifiziert wird, es sich jedoch tatsächlich um eine kompilierte Sprache handelt. Die Ausführung von JavaScript ist in drei Schritte unterteilt: 1. Syntaxanalyse 2. Vorkompilierung 3. Interpretation und Ausführung. Die Syntaxanalyse und die Ausführung der Interpretation sind nicht schwer zu verstehen. Die eine besteht darin, zu überprüfen, ob der Code Syntaxfehler aufweist, und die andere ist für die zeilenweise Ausführung des Programms verantwortlich. Die Vorkompilierungsphase in JavaScript ist jedoch etwas komplizierter.

        Jeder JavaScript-Code muss vor der Ausführung kompiliert werden. In den meisten Fällen erfolgt der Kompilierungsprozess innerhalb weniger Mikrosekunden, bevor der Code ausgeführt wird. Während der Kompilierungsphase startet die JavaScript-Engine mit dem aktuellen Codeausführungsbereich und führt eine RHS-Abfrage für den Code durch, um den Wert der Variablen abzurufen. Während der Ausführungsphase führt die Engine dann die LHS-Abfrage aus und weist den Variablen Werte zu.

​​Während der Kompilierungsphase besteht ein Teil der Aufgabe der JavaScript-Engine darin, alle Deklarationen zu finden und sie dem entsprechenden Bereich zuzuordnen. Wenn es sich während des Vorkompilierungsprozesses im globalen Bereich befindet, erstellt die JavaScript-Engine zunächst ein globales Objekt (GO-Objekt, globales Objekt) im globalen Bereich und fördert die Variablendeklaration und Funktionsdeklaration. Die hochgestufte Variable wird zunächst standardmäßig auf undefiniert initialisiert, und die Funktion stuft den gesamten Funktionskörper hoch. (Wenn die Funktion in Form eines Funktionsausdrucks definiert ist, werden die Regeln der Variablenheraufstufung angewendet. und speichern Sie sie dann in globalen Variablen. Die Heraufstufung von Funktionsdeklarationen hat Vorrang vor der Heraufstufung von Variablendeklarationen. Bei Variablendeklarationen werden wiederholte Var-Deklarationen von der Engine ignoriert, und später erscheinende Funktionsdeklarationen können frühere Funktionsdeklarationen überschreiben (Syntax für neue Variablendeklarationen in ES6). Die Situation ist etwas anders, daher werden wir hier vorerst nicht darauf eingehen).                                                                          Die Aktivierungsphase wird auch innerhalb des Funktionskörpers durchgeführt. Innerhalb des Funktionskörpers wird zunächst ein aktives Objekt

(AO-Objekt, aktives Objekt) erstellt, und die formalen Parameter- und Variablendeklarationen sowie die Funktionsdeklarationen innerhalb des Funktionskörpers werden heraufgestuft. Die formalen Parameter und Variablen werden auf undefiniert initialisiert. Innere Funktionen sind immer noch der innere Funktionskörper selbst und werden dann im aktiven Objekt gespeichert. Nach Abschluss der Kompilierungsphase wird der JavaScript-Code ausgeführt. Der Ausführungsprozess weist Variablen oder formalen Parametern nacheinander Werte zu. Die Engine sucht im Bereich nach entsprechenden Variablendeklarationen oder formalen Parameterdeklarationen und weist ihnen, wenn sie gefunden werden, Werte zu. Wenn im nicht-strikten Modus eine Variable ohne Deklaration zugewiesen wird, erstellt die Engine automatisch und implizit eine Deklaration für die Variable in der globalen Umgebung. Im strikten Modus wird jedoch ein Fehler gemeldet, wenn eine nicht deklarierte Variable zugewiesen wird Wert. . Da die JavaScript-Ausführung Single-Threaded ist, erhalten Sie ein undefiniertes Ergebnis, wenn Sie die Variable

(RHS-Abfrage) abrufen und ausgeben, bevor die Zuweisungsoperation (LHS-Abfrage) ausgeführt wird. Weil der Variablen zu diesem Zeitpunkt noch kein Wert zugewiesen wurde. [ [Umfang]]. Bei Funktionen enthält das Attribut [[Scope]] die Sammlung von Objekten in dem Bereich, in dem die Funktion erstellt wurde – die Bereichskette. Wenn Sie eine Funktion in der globalen Umgebung erstellen, fügt die Bereichskette der Funktion ein globales Objekt ein, das alle im globalen Bereich definierten Variablen enthält.

Der innere Bereich kann auf den äußeren Bereich zugreifen, aber der äußere Bereich kann nicht auf den inneren Bereich zugreifen. Da JavaScript keinen Gültigkeitsbereich auf Blockebene hat, kann auf Variablen, die in einer if-Anweisung oder einer for-Schleifenanweisung definiert sind, außerhalb der Anweisung zugegriffen werden. Vor ES6 verfügte JavaScript nur über einen globalen Bereich und einen Funktionsbereich. ES6 fügte einen neuen Bereichsmechanismus auf Blockebene hinzu.

Wenn die Funktion ausgeführt wird, wird ein internes Objekt namens Ausführungsumgebung (Ausführungskontext, auch Ausführungskontext genannt) für die Ausführungsfunktion erstellt. Jede Ausführungsumgebung verfügt über eine eigene Bereichskette. Wenn eine Ausführungsumgebung erstellt wird, wird die Spitze ihrer Bereichskette zunächst mit dem Objekt im Attribut [[Scope]] der aktuell ausgeführten Funktion initialisiert. Unmittelbar danach wird das aktive Objekt (einschließlich aller lokalen Variablen, benannten Parameter, Argumentparametersätze usw.) , wenn die Funktion ausgeführt wird, ebenfalls erstellt und an die Spitze der Bereichskette verschoben.

Die entsprechende Ausführungsumgebung ist jedes Mal eindeutig, wenn eine Funktion mehrmals ausgeführt wird, was zur Erstellung mehrerer Ausführungsumgebungen führt. Wenn die Funktion die Ausführung abschließt, wird die Ausführungsumgebung zerstört. Wenn die Ausführungsumgebung zerstört wird, wird auch das aktive Objekt zerstört (Die globale Ausführungsumgebung wird erst zerstört, wenn die Anwendung beendet wird, z. B. wenn die Webseite oder der Browser geschlossen wird) .

​​​Während der Ausführung der Funktion durchläuft diese jedes Mal, wenn eine Variable angetroffen wird, einen Identifikator-Analyseprozess, um zu bestimmen, wo die Daten abgerufen oder gespeichert werden sollen. Bei der Bezeichnerauflösung wird Ebene für Ebene entlang der Bereichskette gesucht. Die globale Variable ist immer das letzte Objekt (d. h. das Fensterobjekt) der Bereichskette.

In JavaScript gibt es zwei Anweisungen, die die Scope-Kette während der Ausführung vorübergehend ändern können. Die erste ist die with-Anweisung. Die with-Anweisung erstellt ein veränderliches Objekt, das alle Eigenschaften des durch den Parameter angegebenen Objekts enthält, und verschiebt das Objekt an die erste Position der Bereichskette, was bedeutet, dass das aktive Objekt der Funktion an die zweite Position des gequetscht wird Scope-Kette. Obwohl dadurch der Zugriff auf Eigenschaften veränderlicher Objekte sehr schnell erfolgt, wird der Zugriff auf lokale Variablen usw. langsamer. Die zweite Anweisung, die die Bereichskette der Ausführungsumgebung ändern kann, ist die Catch-Klausel in der Try-Catch-Anweisung. Wenn im Try-Codeblock ein Fehler auftritt, springt der Ausführungsprozess automatisch zur Catch-Klausel, und dann wird das Ausnahmeobjekt in ein Variablenobjekt verschoben und oben im Bereich platziert. Innerhalb des Catch-Blocks werden alle lokalen Variablen der Funktion im zweiten Scope-Chain-Objekt platziert. Sobald die Catch-Klausel ausgeführt wird, kehrt die Bereichskette in ihren vorherigen Zustand zurück.

Konstruktor

Der Konstruktor in JavaScript kann zum Erstellen von Objekten bestimmter Typen verwendet werden. Um sie von anderen Funktionen zu unterscheiden, beginnen Konstruktoren im Allgemeinen mit einem Großbuchstaben. Dies ist in JavaScript jedoch nicht notwendig, da JavaScript keine spezielle Syntax zur Definition von Konstruktoren hat. In JavaScript besteht der einzige Unterschied zwischen Konstruktoren und anderen Funktionen in der Art und Weise, wie sie aufgerufen werden. Jede Funktion kann als Konstruktor verwendet werden, solange sie über den new-Operator aufgerufen wird.

JavaScript Die Funktion verfügt über vier Aufrufmodi: 1. Unabhängiger Funktionsaufrufmodus, z. B. foo(arg). 2. Methodenaufrufmodus, z. B. obj.foo(arg). 3. Konstruktoraufrufmodus, z. B. new foo(arg). 4. Aufrufmodus aufrufen/anwenden, z. B. foo.call(this,arg1,arg2) oder foo.apply(this,args) (args hier ist ein Array).

Um eine Instanz des Konstruktors zu erstellen und die Rolle des Konstruktors zu übernehmen, müssen Sie den neuen Operator verwenden. Wenn wir den neuen Operator verwenden, um einen Konstruktor zu instanziieren, werden die folgenden Schritte innerhalb des Konstruktors ausgeführt:
  1. Implizit ein dieses leeres Objekt erstellen
  2. Den Code im Konstruktor ausführen (Attribute zum aktuellen dieses Objekt hinzufügen)
3. Gibt implizit das aktuelle Objekt zurück. Wenn der Konstruktor explizit ein Objekt zurückgibt, ist die Instanz das zurückgegebene Objekt, andernfalls ist es das implizit zurückgegebene Objekt.

Wenn wir den Konstruktor aufrufen, um eine Instanz zu erstellen, verfügt die Instanz über alle Instanzattribute und Methoden des Konstruktors. Für verschiedene Instanzen, die durch Konstruktoren erstellt wurden, sind ihre Instanzeigenschaften und -methoden unabhängig. Selbst wenn es sich um Referenztypwerte mit demselben Namen handelt, wirken sich verschiedene Instanzen nicht gegenseitig aus.

Prototyp und Prototypkette

Prototyp und Prototypkette sind nicht nur eine der Essenzen der JavaScript-Sprache, sondern auch eine der Schwierigkeiten dieser Sprache. Der Prototyp-Prototyp (expliziter Prototyp) ist ein eindeutiges Attribut einer Funktion. Immer wenn eine Funktion erstellt wird, erstellt die Funktion automatisch ein Prototyp-Attribut und zeigt auf das Prototyp-Objekt der Funktion. Alle Prototypobjekte erhalten automatisch ein Konstruktor-Attribut (Konstruktor, das auch als Konstruktor übersetzt werden kann). Dieses Attribut enthält einen Zeiger auf die Funktion (d. h. den Konstruktor selbst) Das Prototypattribut befindet sich. Wenn wir eine Instanz über den Konstruktor erstellen, enthält die Instanz eine interne Eigenschaft von [[Prototype]] (impliziter Prototyp), die auch auf das Prototypobjekt des Konstruktors verweist. In Firefox, Safari und Chrome kann jedes Objekt über das Attribut __proto__ auf seine [[Prototype]]-Eigenschaften zugreifen. Bei anderen Browsern ist dieses Attribut für Skripte völlig unsichtbar. ​​ Das Prototypattribut des Konstruktors und der [[Prototyp]] der Instanz verweisen beide auf das Prototypobjekt des Konstruktors. Es besteht keine direkte Beziehung zwischen dem [[Prototyp]]-Attribut der Instanz und des Konstruktors. Um herauszufinden, ob die Eigenschaft [[Prototype]] einer Instanz auf das Prototypobjekt eines bestimmten Konstruktors verweist, können wir die Methode isPrototypeOf() oder Object.getPrototypeOf() verwenden.

Immer wenn eine Eigenschaft einer Objektinstanz gelesen wird, wird eine Suche durchgeführt, die auf die Eigenschaft mit dem angegebenen Namen abzielt. Die Suche beginnt zunächst bei der Objektinstanz selbst. Wenn in der Instanz ein Attribut mit einem bestimmten Namen gefunden wird, wird der Wert des Attributs zurückgegeben. Wenn dieser nicht gefunden wird, wird die Suche nach dem Prototypobjekt fortgesetzt, auf das der [[Prototyp] zeigt. ]-Attribut des Objekts. Sucht im Prototyp ein Objekt nach einer Eigenschaft mit einem bestimmten Namen und gibt den Wert der Eigenschaft zurück, wenn sie gefunden wird.

Um festzustellen, von welchem ​​Konstruktor das Objekt eine direkte Instanz ist, können Sie direkt auf die Konstruktoreigenschaft der Instanz zugreifen. Die Instanz liest die Konstruktoreigenschaft des Prototypobjekts über [[Prototyp]“. ] und den Konstruktor selbst zurückgeben.

Auf die Werte im Prototypobjekt kann über die Objektinstanz zugegriffen werden, sie können jedoch nicht über die Objektinstanz geändert werden. Wenn wir in der Instanz eine Eigenschaft mit demselben Namen wie das Instanz-Prototypobjekt hinzufügen, erstellen wir die Eigenschaft in der Instanz. Diese Instanzeigenschaft verhindert, dass wir auf diese Eigenschaft im Prototypobjekt zugreifen, ändert diese Eigenschaft jedoch nicht. Durch einfaches Setzen der Instanzeigenschaft auf Null wird der Zugriff auf die Eigenschaft im Prototypobjekt nicht wiederhergestellt. Um den Zugriff auf die Eigenschaft im Prototypobjekt wiederherzustellen, können Sie den Löschoperator verwenden, um die Eigenschaft vollständig aus der Objektinstanz zu löschen.

Verwenden Sie die Methode hasOwnProperty(), um zu erkennen, ob eine Eigenschaft in der Instanz oder im Prototyp vorhanden ist. Diese Methode gibt nur dann true zurück, wenn die angegebene Eigenschaft in der Objektinstanz vorhanden ist. Um alle aufzählbaren Instanzeigenschaften des Objekts selbst zu erhalten, können Sie die ES5-Methode Object.keys() verwenden. Um alle Instanzeigenschaften abzurufen, ob aufzählbar oder nicht, können Sie die Methode Object.getOwnPropertyNames() verwenden.

Prototyp ist dynamisch und alle am Prototypobjekt vorgenommenen Änderungen können sofort auf die Instanz übertragen werden. Wenn jedoch das gesamte Prototypobjekt neu geschrieben wird, ist die Situation anders. Durch den Aufruf des Konstruktors wird der Objektinstanz ein [[Prototyp]]-Zeiger auf das ursprüngliche Prototypobjekt hinzugefügt. Nach dem Umschreiben des gesamten Prototypobjekts zeigt der Konstruktor auf das neue Prototypobjekt. auf dem Objekt; und die Objektinstanz zeigt auch auf das ursprüngliche Prototypobjekt, sodass die Verbindung zwischen dem Konstruktor und dem ursprünglichen Prototypobjekt, die auf dasselbe Prototypobjekt zeigen, getrennt wird, da sie jeweils auf unterschiedliche Prototypobjekte verweisen.

Um diese Verbindung wiederherzustellen, können Sie die Objektinstanz instanziieren, nachdem der Konstruktor-Prototyp neu geschrieben wurde, oder das __proto__-Attribut der Objektinstanz ändern, um auf das neue Prototypobjekt des Konstruktors zu verweisen.

JavaScript verwendet die Prototypenkette als Hauptmethode zur Implementierung der Vererbung. Es verwendet Prototypen, um einem Referenztyp die Eigenschaften und Methoden eines anderen Referenztyps erben zu lassen. Die Instanz des Konstruktors verfügt über ein [[Prototype]]-Attribut, das auf das Prototypobjekt zeigt. Wenn wir das Prototypobjekt des Konstruktors einer Instanz eines anderen Typs gleichsetzen, enthält das Prototypobjekt auch einen [[Prototype]]-Zeiger zum anderen Prototyp. Wenn ein anderer Prototyp eine Instanz eines anderen Typs ist ... und so weiter, wird eine Kette von Instanzen und Prototypen gebildet. Dies ist das Grundkonzept der sogenannten Prototypenkette.

Die Prototypenkette erweitert den Prototypen-Suchmechanismus. Beim Lesen eines Instanzattributs wird zunächst in der Instanz nach dem Attribut gesucht. Wenn das Attribut nicht gefunden wird, wird die Suche nach dem Prototypobjekt, auf das die Instanz [[Prototyp]] zeigt, fortgesetzt. Wenn das Prototypobjekt nicht gefunden wird, wird die Suche fortgesetzt . Das Prototypobjekt [[Prototyp]] zeigt auf ein anderes Prototypobjekt ... Der Suchvorgang wird entlang der Prototypkette weiter gesucht. Wenn das angegebene Attribut oder die angegebene Methode nicht gefunden werden kann, wird der Suchvorgang nacheinander ausgeführt Die Prototypenkette wird am Ende aufhören.

Wenn das Prototypobjekt der Funktion nicht geändert wird, verfügen alle Referenztypen über ein [[Prototype]]-Attribut, das standardmäßig auf das Prototypobjekt von Object verweist. Daher ist der Standardprototyp aller Funktionen eine Instanz von Object, was der Hauptgrund dafür ist, dass alle benutzerdefinierten Typen Standardmethoden wie toString() und valueOf() erben. Sie können den Operator „instanceof“ oder die Methode „isPrototypeOf()“ verwenden, um festzustellen, ob ein Konstruktorprototyp in der Prototypenkette der Instanz vorhanden ist.

Obwohl die Prototypenkette sehr leistungsfähig ist, weist sie auch einige Probleme auf. Das erste Problem besteht darin, dass der Referenztypwert des Prototypobjekts von allen Instanzen gemeinsam genutzt wird. Dies bedeutet, dass die Referenztypeigenschaften oder -methoden verschiedener Instanzen auf denselben Heapspeicher verweisen alle anderen Instanzen gleichzeitig Der Referenzwert der Instanz auf dem Prototypobjekt, weshalb private Eigenschaften oder Methoden im Konstruktor und nicht auf dem Prototyp definiert werden. Das zweite Problem mit der Prototypkette besteht darin, dass, wenn wir den Prototyp eines Konstruktors mit einer Instanz eines anderen Konstruktors gleichsetzen und zu diesem Zeitpunkt Parameter an einen anderen Konstruktor übergeben, um Attributwerte festzulegen, alle Eigenschaften auf dem Original basieren Konstruktor wird diesem Attribut der Instanz aufgrund der Prototypenkette derselbe Wert zugewiesen, und dies ist manchmal nicht das gewünschte Ergebnis.

Closure

Closure bezeichnet in JavaScript eine Funktion, die das Recht hat, auf Variablen im Rahmen einer anderen Funktion zuzugreifen Daten außerhalb des lokalen Bereichs. Eine übliche Methode zum Erstellen eines Abschlusses besteht darin, eine Funktion innerhalb einer anderen Funktion zu erstellen und diese Funktion zurückzugeben.

Im Allgemeinen wird beim Ausführen der Funktion das lokal aktive Objekt zerstört und nur der globale Bereich im Speicher gespeichert. Anders verhält es sich jedoch bei Schließungen.

Das [[Scope]]-Attribut der Abschlussfunktion wird auf die Bereichskette der Funktion initialisiert, die es umschließt, sodass der Abschluss einen Verweis auf dasselbe Objekt wie die Bereichskette der Ausführungsumgebung enthält. Im Allgemeinen wird das aktive Objekt der Funktion zusammen mit der Ausführungsumgebung zerstört. Wenn jedoch ein Abschluss eingeführt wird, kann das aktive Objekt der ursprünglichen Funktion nicht zerstört werden, da die Referenz weiterhin im Attribut [[Scope]] des Abschlusses vorhanden ist. Dies bedeutet, dass Abschlussfunktionen mehr Speicheraufwand erfordern als Nicht-Abschlussfunktionen, was zu mehr Speicherverlusten führt. Wenn ein Abschluss auf das aktive Objekt der ursprünglichen Wrapping-Funktion zugreift, muss er außerdem zuerst die Kennung seines eigenen aktiven Objekts in der Bereichskette auflösen und die obere Ebene finden. Daher verwendet der Abschluss die Variablen der ursprünglichen Wrapping-Funktion ist auch leistungsschädlich und hat große Auswirkungen.

    Bei Timern, Ereignis-Listenern, Ajax-Anfragen, fensterübergreifender Kommunikation, Web Workern oder anderen asynchronen oder synchronen Aufgaben verwenden Sie tatsächlich Abschlüsse, solange Sie Rückruffunktionen verwenden.

                                                                                                                                       IN     IN IN IN IN TO IN IN IN IN IN IN 1, 2, 3. Die tatsächliche Situation ist jedoch, dass die vier von diesem Code ausgegebenen Zahlen alle 4 sind.

Da es sich bei dem Timer um einen asynchronen Lademechanismus handelt, wird er erst ausgeführt, wenn die for-Schleife durchlaufen wird. Bei jeder Ausführung des Timers sucht der Timer in seinem äußeren Bereich nach der Variablen i. Da die Schleife beendet wurde, wurde die i-Variable im externen Bereich auf 4 aktualisiert, sodass die von den vier Timern erhaltenen i-Variablen alle 4 sind, statt unserer idealen Ausgabe von 0, 1, 2, 3.

Um dieses Problem zu lösen, können wir einen neuen Bereich erstellen, der die Funktion zur sofortigen Ausführung umschließt, die i-Variable des externen Bereichs in jeder Schleife im neu erstellten Bereich speichern und den Timer jedes Mal, wenn wir können, vom neuen Bereich starten lassen Verwenden Sie die Funktion zur sofortigen Ausführung, um diesen neuen Bereich zu erstellen:

Auf diese Weise gibt das Ergebnis der Schleifenausführung nacheinander 0, 1, 2, 3 aus. Wir können diese Funktion zur sofortigen Ausführung auch erneut verwenden Ein bisschen, übergeben Sie den Aktionsparameter i direkt an die Funktion zur sofortigen Ausführung, sodass j darin kein Wert zugewiesen werden muss:

Natürlich ist es nicht erforderlich, die Funktion zur sofortigen Ausführung zu verwenden Erstellen Sie auch eine nicht anonyme Funktion, die bei jeder Schleife ausgeführt wird, aber zum Speichern der Funktionsdeklaration mehr Speicher benötigt.

Da es vor ES6 keine Bereichseinstellung auf Blockebene gab, können wir dieses Problem nur lösen, indem wir manuell einen neuen Bereich erstellen. ES6 hat damit begonnen, den Bereich auf Blockebene festzulegen. Wir können let verwenden, um den Bereich auf Blockebene zu definieren:

Der let-Operator erstellt einen Bereich auf Blockebene und die von let deklarierten Variablen werden auf der aktuellen Blockebene gespeichert Umfang, sodass jede sofort ausgeführte Funktion jedes Mal nach Variablen aus ihrem aktuellen Blockbereich sucht.个LET hat auch eine spezielle Definition. Dadurch wird die Variable nicht nur einmal während des Zyklus deklariert, sondern der Wert der neuen Anweisung wird mit dem Wert am Ende des Zyklus initialisiert der Kopf der for-Schleife:

this zeigt auf Dieses Schlüsselwort ist einer der komplexesten Mechanismen in JavaScript und wird automatisch im Umfang aller Funktionen definiert. Es ist leicht zu verstehen, dass dies auf die Funktion selbst verweist. In ES5 ist dies jedoch nicht gebunden, wenn die Funktion ausgeführt wird. Es hat nichts damit zu tun, wo die Funktion deklariert wird.

(Dies ist in der neuen Pfeilfunktion in ES6 anders und ihre Ausrichtung hängt von der Position der Funktionsdeklaration ab.) Denken Sie an die vier Funktionsaufrufmodi, die ich zuvor erwähnt habe: 1.

Unabhängiges Funktionsaufrufmuster

, wie foo(arg). 2.Objektmethoden-Aufrufmodus, z. B. obj.foo(arg). 3.Konstruktor-Aufrufmodus, z. B. new foo(arg). 4.call/applyAnrufmodus, wie zum Beispiel foo.call(this) oder foo.apply(this). Im unabhängigen Funktionsaufrufmodus zeigt dies im nicht-strikten Modus standardmäßig auf das globale Objekt. Im strikten Modus darf dies standardmäßig nicht an das globale Objekt gebunden werden, daher wird es an undefiniert gebunden.

Für den Objektmethoden-Aufrufmodus zeigt dies in der Funktion auf das Objekt selbst, das es aufruft:

Für den Konstruktor-Aufrufmodus wurden die Ausführungsschritte innerhalb des Konstruktors bereits eingeführt:

1 . Erstellen Sie implizit ein leeres This-Objekt

2. Führen Sie den Code im Konstruktor aus (fügen Sie Attribute zum aktuellen This-Objekt hinzu)
3. Geben Sie implizit das aktuelle This-Objekt zurück

Daher ist es beim Aufrufen einer Funktion mit einer neuen Methode das Zeiger auf Dieses Objekt wird implizit und unabhängig im Konstruktor erstellt. Alle dadurch hinzugefügten Eigenschaften oder Methoden werden schließlich zu diesem leeren Objekt hinzugefügt und an die Instanz des Konstruktors zurückgegeben.

Für den Call/Apply-Aufrufmodus wird dies in der Funktion an den ersten Parameter gebunden, den Sie übergeben, wie in der Abbildung gezeigt:

foo.apply() und foo.call() Die Funktion zum Ändern des Punktes, auf den dies zeigt, ist dieselbe. Der einzige Unterschied besteht darin, ob der zweite Parameter im Array-Format oder im Streuparameterformat übergeben wird.

Über die zugrunde liegenden Prinzipien von JavaScript werde ich heute hier vorübergehend schreiben und den Inhalt über JavaScript weiterhin aktualisieren.

【Verwandte Empfehlungen:

Javascript-Lern-Tutorial

Das obige ist der detaillierte Inhalt vonVertiefendes Verständnis der zugrunde liegenden JS-Mechanismen wie JS-Datentypen, Vorkompilierung und Ausführungskontext. 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