JavaScript ist eine objektorientierte Sprache. In JavaScript gibt es ein sehr klassisches Sprichwort: Alles ist ein Objekt. Da es objektorientiert ist, gibt es drei Hauptmerkmale von objektorientiertem: Kapselung, Vererbung und Polymorphismus. Hier sprechen wir über das Erbe von JavaScript und werden später über die anderen beiden sprechen.
Die Vererbung von JavaScript ist nicht genau der von C ++. Die Vererbung von C ++ basiert auf der Klasse, während die Vererbung von JavaScript auf Prototypen basiert.
Jetzt ist das Problem hier.
Was ist der Prototyp? Für Prototypen können wir uns auf die Klassen in C ++ beziehen und die Eigenschaften und Methoden des Objekts speichern. Lassen Sie uns zum Beispiel ein einfaches Objekt schreiben
Die Codekopie lautet wie folgt:
Funktion Animal (Name) {
this.name = name;
}
Animal.Prototype.SetName = Funktion (Name) {
this.name = name;
}
var Animal = New Animal ("Wangwang");
Wir können sehen, dass dies ein Objekttier ist, das einen Attributnamen und einen Methode -SetName hat. Beachten Sie, dass sobald der Prototyp geändert wurde, z. B. das Hinzufügen einer Methode, alle Instanzen des Objekts diese Methode teilen. Zum Beispiel
Die Codekopie lautet wie folgt:
Funktion Animal (Name) {
this.name = name;
}
var Animal = New Animal ("Wangwang");
Zu diesem Zeitpunkt hat Animal nur das Namensattribut. Wenn wir einen Satz hinzufügen,
Die Codekopie lautet wie folgt:
Animal.Prototype.SetName = Funktion (Name) {
this.name = name;
}
Zu diesem Zeitpunkt wird Tier auch eine SetName -Methode haben.
Vererbungskopie - Aus einem leeren Objekt wissen wir, dass es unter den grundlegenden Arten von JS einen Typ namens Objekt gibt, und seine grundlegendste Instanz ist ein leeres Objekt, dh die Instanz, die durch Aufrufen eines neuen Objekts () direkt oder mit dem buchstäblichen {} deklariert wird. Ein leeres Objekt ist ein "sauberes Objekt" mit nur vordefinierten Eigenschaften und Methoden, während alle anderen Objekte von leeren Objekten geerbt werden, sodass alle Objekte diese vordefinierten Eigenschaften und Methoden haben. Der Prototyp ist eigentlich eine Objektinstanz. Die Bedeutung eines Prototyps lautet: Wenn der Konstruktor über ein Prototypobjekt A verfügt, müssen die vom Konstruktor erstellten Instanzen von A kopiert werden, da die Instanz aus Objekt A kopiert wird, die Instanz muss alle Eigenschaften, Methoden und anderen Eigenschaften von A erben. Wie wird die Replikation implementiert? Methode 1: Konstrukte Kopie jeder konstruierten Instanz wird aus dem Prototyp kopiert und die neue Instanz nimmt den gleichen Speicherraum wie der Prototyp ein. Obwohl dies OBJ1 und OBJ2 mit ihren Prototypen "vollständig konsistent" machen, ist es auch sehr unwirtschaftlich - der Verbrauch des Gedächtnisraums wird rasch zunehmen. Wie im Bild gezeigt:
Methode 2: Kopie beim Schreiben dieser Strategie stammt aus der Technologie eines konsistenten Täuschungssystems: Kopie beim Schreiben. Ein typisches Beispiel für diese Art von Betrug ist die Dynamic Link Library (DDL) im Betriebssystem, dessen Speicherbereich immer beim Schreiben kopiert wird. Wie im Bild gezeigt:
Wir müssen nur in dem System angeben, dass OBJ1 und OBJ2 ihren Prototypen entsprechen. Im Lesen müssen wir also nur die Anweisungen befolgen, um den Prototyp zu lesen. Wenn wir die Eigenschaften eines Objekts (z. B. OBJ2) schreiben müssen, kopieren wir ein Prototypbild und leiten nachfolgende Operationen auf das Bild hinweisen. Wie im Bild gezeigt:
Der Vorteil dieser Methode besteht darin, dass wir beim Erstellen von Instanzen und Leseattributen nicht viel Speicheraufwand benötigen. Wir verwenden nur einen Code, um beim ersten Mal Speicher zuzuweisen und etwas Code und Speicheraufwand mitzubringen. Seitdem wird es jedoch keinen solchen Overhead geben, da die Effizienz des Zugriffs von Bildern und dem Zugriff auf Prototypen konsistent ist. Bei Systemen, die häufig Operationen schreiben, ist diese Methode jedoch nicht wirtschaftlich als die vorherige Methode. Methode 3: Lesen von Traversal Diese Methode macht die Granularität der Kopie vom Prototyp in Mitglied. Diese Methode wird durch das Kopieren der Mitgliedsinformationen in das Instanzbild nur beim Schreiben eines Mitglieds einer Instanz gekennzeichnet. Wenn Sie beispielsweise Objekteigenschaften (obj2.Value = 10) schreiben, wird ein Attributwert mit dem Namen namens Wert generiert und in die Mitgliedsliste des Obj2 -Objekts platziert. Schauen Sie sich das Bild an:
Es kann festgestellt werden, dass OBJ2 immer noch ein Hinweis auf den Prototyp ist und während der Operation keine Objektinstanzen derselben Größe wie der Prototyp erstellt wurden. Auf diese Weise führen Schreibvorgänge nicht zu einer großen Menge an Speicherallokation, sodass die Speicherverwendung wirtschaftlich erscheint. Der Unterschied besteht darin, dass OBJ2 (und alle Objektinstanzen) eine Mitgliedsliste verwalten müssen. Diese Mitgliedsliste folgt zwei Regeln: Es wird garantiert, dass sie beim Lesen zuerst zugegriffen werden. Wenn im Objekt kein Attribut angegeben ist, versuchen Sie, die gesamte Prototypkette des Objekts zu durchqueren, bis der Prototyp leer oder die Eigenschaft gefunden wird. Die Prototypkette wird später diskutiert. Unter den drei Methoden ist Lesetraversal unter den drei Methoden die beste Leistung. Daher wird der Prototyp -Vererbung von JavaScript TRAversal gelesen. Konstruktoren, die mit C ++ vertraut sind, werden definitiv verwirrt, nachdem sie den Code des Top -Objekts gelesen haben. Ohne das Schlüsselwort der Klasse ist es leicht zu verstehen. Schließlich gibt es Funktions Schlüsselwörter, aber die Schlüsselwörter sind unterschiedlich. Aber was ist mit dem Konstruktor? Tatsächlich hat JavaScript auch ähnliche Konstruktoren, aber sie werden als Konstruktoren bezeichnet. Bei der Verwendung des neuen Operators wurde der Konstruktor aufgerufen und dies ist als Objekt gebunden. Zum Beispiel verwenden wir den folgenden Code
Die Codekopie lautet wie folgt:
var Animal = Animal ("Wangwang");
Tier wird undefiniert sein. Einige Leute werden sagen, dass kein Rückgabewert natürlich undefiniert ist. Wenn Sie dann die Definition von Tierobjekten ändern:
Die Codekopie lautet wie folgt:
Funktion Animal (Name) {
this.name = name;
gib dies zurück;
}
Ratet mal, welches Tier jetzt ist?
Zu diesem Zeitpunkt ist Tier zu Fenster geworden. Der Unterschied besteht darin, dass es das Fenster erweitert, so dass das Fenster ein Namensattribut hat. Dies liegt daran, dass diese Standards für das Fenster, dh die Variable der obersten Ebene, ohne sie anzugeben. Nur durch Aufrufen des neuen Schlüsselworts kann der Konstruktor korrekt aufgerufen werden. Wie kann man also vermeiden, dass das neue Keyword von der Person, die es verwendet, übersehen wird? Wir können einige kleinere Änderungen vornehmen:
Die Codekopie lautet wie folgt:
Funktion Animal (Name) {
if (! (dieses Instanz des Tieres)) {
Neues Tier zurückgeben (Name);
}
this.name = name;
}
Dies wird narrensicher sein. Der Konstruktor hat auch eine andere Verwendung, die angibt, zu welchem Objekt die Instanz gehört. Wir können Instanz verwenden, um zu beurteilen, aber Instanz von Ancestor -Objekten und realen Objekten beim Erben kehrt treu zurück, so dass es nicht sehr geeignet ist. Wenn der Konstruktor als neu bezeichnet wird, weist er standardmäßig auf das aktuelle Objekt hin.
Die Codekopie lautet wie folgt:
console.log (Animal.Prototype.Constructor === Animal); // WAHR
Wir können anders denken: Der Prototyp ist zu Beginn der Funktion wertlos, und die Implementierung kann die folgende Logik sein
// set __Proto__ ist ein integriertes Mitglied der Funktion, get_prototyoe () ist seine Methode
Die Codekopie lautet wie folgt:
var __Proto__ = null;
Funktion get_prototype () {
if (! __ proto__) {
__Proto__ = neues Objekt ();
__Proto __. Konstruktor = dies;
}
return __Proto__;
}
Dieser Vorteil ist, dass es vermeidet, dass für jede deklarierte Funktion eine Objektinstanz erstellt wird, wodurch der Aufwand speichert. Der Konstruktor kann geändert werden, was später diskutiert wird. Ich glaube, jeder weiß, was die Vererbung auf Prototypen basiert, sodass sie seine IQ -Untergrenze nicht vorstellen.
Es gibt verschiedene Arten von JS -Vererbung, hier sind zwei Arten
1. Methode 1 Diese Methode wird am häufigsten verwendet und hat eine bessere Sicherheit. Definieren wir zuerst zwei Objekte
Die Codekopie lautet wie folgt:
Funktion Animal (Name) {
this.name = name;
}
Funktionshund (Alter) {
this.age = Alter;
}
var hund = neuer hund (2);
Es ist sehr einfach, die Vererbung zu konstruieren, auf den Prototyp des untergeordneten Objekts auf die Instanz des übergeordneten Objekts zu richten (beachten Sie, dass es sich um eine Instanz handelt, kein Objekt).
Die Codekopie lautet wie folgt:
Dog.Prototype = New Animal ("Wangwang");
Zu diesem Zeitpunkt hat Hund zwei Attribute, Namen und Alter. Und wenn der Instanz des Bedieners für Hund verwendet wird
Die Codekopie lautet wie folgt:
console.log (Hundeinstanz des Tieres); // WAHR
console.log (Hundinstanz des Hundes); // FALSCH
Dies erreicht Vererbung, aber es gibt ein kleines Problem
Die Codekopie lautet wie folgt:
console.log (dog.prototype.Constructor === Tier); // WAHR
console.log (dog.prototype.Constructor === Hund); // FALSCH
Sie können sehen, dass sich das vom Konstruktor veränderte Objekt geändert hat, was unseren Zweck nicht erfüllt. Wir können nicht bestimmen, wem die Instanz, zu der wir neu gehört,. Daher können wir einen Satz hinzufügen:
Die Codekopie lautet wie folgt:
Dog.Prototype.Constructor = Hund;
Schauen wir uns noch einmal an:
Die Codekopie lautet wie folgt:
console.log (Hundeinstanz des Tieres); // FALSCH
console.log (Hundinstanz des Hundes); // WAHR
Erledigt. Diese Methode ist eine Verbindung bei der Aufrechterhaltung der Prototypkette, die im Folgenden ausführlich erläutert wird. 2. Methode 2 Diese Methode hat ihre Vorteile und Nachteile, aber die Nachteile überwiegen die Vorteile. Schauen Sie sich zuerst den Code an
Die Codekopie lautet wie folgt:
<pre name = "code"> Funktion Animal (Name) {
this.name = name;
}
Animal.Prototype.SetName = Funktion (Name) {
this.name = name;
}
Funktionshund (Alter) {
this.age = Alter;
}
Dog.Prototype = Animal.Prototyp;
Dies ermöglicht das Kopieren des Prototyps.
Der Vorteil dieser Methode besteht darin, dass sie nicht die Instanziierung von Objekten (im Vergleich zu Methode 1) und Ressourcen speichern muss. Die Nachteile sind ebenfalls offensichtlich. Zusätzlich zu dem gleichen Problem wie oben zeigt der Konstruktor auf das übergeordnete Objekt nur die vom übergeordneten Objekt mit Prototyp deklarierten Eigenschaften und Methoden. Das heißt, im obigen Code kann das Namensattribut des Tierobjekts nicht kopiert werden, aber die SetName -Methode kann kopiert werden. Am fatalsten ist, dass jede Änderung des Prototyps des untergeordneten Objekts den Prototyp des übergeordneten Objekts beeinflusst, dh die von beiden Objekten deklarierten Fälle werden betroffen. Daher wird diese Methode nicht empfohlen.
Prototypkette
Jeder, der über Erbschaft geschrieben hat, weiß, dass die Erbschaft aus mehreren Ebenen erbt werden kann. Und in JS bildet dies die Prototypkette. Der obige Artikel erwähnte auch die Prototypkette mehrmals. Was ist also die Prototypkette? Eine Instanz sollte zumindest ein Protoattribut auf den Prototyp zeigen, was die Grundlage des Objektsystems in JavaScript ist. Diese Eigenschaft ist jedoch unsichtbar, und wir nennen sie "interne Prototypkette", um sie von der "Konstruktorprototypkette" zu unterscheiden, die aus dem Prototyp des Konstruktors besteht (dh das, was wir normalerweise "Prototyp -Kette" nennen). Erstellen wir zunächst eine einfache Vererbungsbeziehung gemäß dem obigen Code:
Die Codekopie lautet wie folgt:
Funktion Animal (Name) {
this.name = name;
}
Funktionshund (Alter) {
this.age = Alter;
}
var Animal = New Animal ("Wangwang");
Hunde.Prototyp = Tier;
var hund = neuer hund (2);
Wie bereits erwähnt, erben alle Objekte leere Objekte. Also konstruieren wir eine Prototypkette:
Wir können sehen, dass der Prototyp des untergeordneten Objekts auf die Instanz des übergeordneten Objekts hinweist und die Konstruktor -Prototyp -Kette bildet. Das innere Protoobjekt der untergeordneten Instanz ist auch eine Instanz, die auf das übergeordnete Objekt zeigt und eine interne Prototypkette bildet. Wenn wir eine Eigenschaft finden müssen, ist der Code ähnlich wie
Die Codekopie lautet wie folgt:
Funktion getattrfromobj (attr, obj) {
if (typeof (obj) === "Objekt") {
var Proto = obj;
while (proto) {
if (proto.hasownProperty (attr)) {
Return Proto [Attr];
}
proto = proto .__ proto__;
}
}
und definierte zurückkehren;
}
Wenn wir in diesem Beispiel nach dem Namensattribut in Hund suchen, wird es in der Mitgliedsliste in Hunde durchsucht. Natürlich wird es nicht gefunden, denn jetzt ist die Mitgliedsliste von Hund nur das Elementalter. Dann wird es weiter entlang der Prototyp -Kette suchen, dh der Instanz, auf die .Proto zeigt, dh im Tier finden das Namensattribut und senden Sie es zurück. Wenn es sich bei der Suche um eine Eigenschaft handelt, die nicht vorhanden ist, wenn sie nicht im Tier gefunden werden kann, wird sie weiterhin mit .Proto suchen, ein leeres Objekt finden und dann weiter mit .Proto und dem .Proto der leeren Objektpunkte nach Null gesucht und nach dem Ausgang gesucht.
Aufrechterhaltung von Prototyp -Ketten Wir haben eine Frage aufgeworfen, als wir gerade über Prototypenvereritanz sprachen. Bei der Verwendung von Methode 10 zum Konstruktion der Vererbung verweist der Konstruktor der untergeordneten Objektinstanz auf das übergeordnete Objekt. Der Vorteil davon ist, dass wir über das Konstruktorattribut auf die Prototypkette zugreifen können, und die Nachteile sind ebenfalls offensichtlich. Ein Objekt, dessen Instanz auf sich selbst zeigen sollte, d.h.
Die Codekopie lautet wie folgt:
(neuer obj ()). prototyp.Constructor === obj;
Nachdem wir die Prototyp -Eigenschaften neu geschrieben haben, zeigt der Konstruktor der vom untergeordneten Objekt erzeugten Instanz nicht auf sich selbst! Dies verstößt gegen die ursprüngliche Absicht des Konstruktors. Wir haben oben eine Lösung erwähnt:
Die Codekopie lautet wie folgt:
Dog.Prototype = New Animal ("Wangwang");
Dog.Prototype.Constructor = Hund;
Es scheint, dass nichts falsch ist. Tatsächlich bringt dies jedoch ein neues Problem auf, da wir feststellen werden, dass wir nicht zur Prototypkette zurückkehren können, da wir das übergeordnete Objekt nicht finden können und das .Proto -Attribut der internen Prototyp -Kette nicht zugänglich ist. Daher bietet Spiderermonkey eine Verbesserung: Fügen Sie einem erstellten Objekt eine Eigenschaft mit dem Namen __Proto__ hinzu, die immer auf den vom Konstruktor verwendeten Prototyp hinweist. Auf diese Weise wirkt sich jede Änderung des Konstruktors nicht auf den Wert von __Proto__ aus, wodurch es bequem ist, den Konstruktor aufrechtzuerhalten.
Es gibt jedoch zwei weitere Probleme:
__Proto__ ist neu abgeschrieben, was bedeutet, dass es bei der Verwendung immer noch Risiken gibt
__Proto__ ist eine besondere Verarbeitung von Spidermonkey und kann nicht in anderen Motoren verwendet werden (wie JSCript).
Es gibt eine andere Möglichkeit für uns, die Konstruktoreigenschaften des Prototyps aufrechtzuerhalten und die Konstruktoreigenschaften der Instanz innerhalb der Subklassenkonstruktorfunktion zu initialisieren.
Der Code lautet wie folgt: Schreiben Sie das untergeordnete Objekt um
Die Codekopie lautet wie folgt:
Funktionshund (Alter) {
this.constructor = argumente.callee;
this.age = Alter;
}
Dog.Prototype = New Animal ("Wangwang");
Auf diese Weise verweist der Konstruktor aller Fälle von untergeordneten Objekten korrekt auf das Objekt, während der Konstruktor des Prototyps auf das übergeordnete Objekt zeigt. Obwohl diese Methode relativ ineffizient ist, da das Konstruktorattribut jedes Mal, wenn die Instanz konstruiert wird, neu geschrieben werden muss, besteht kein Zweifel daran, dass diese Methode die vorherigen Widersprüche effektiv lösen kann. ES5 berücksichtigt dies und löst dieses Problem vollständig: Object.getPrototypeof () kann jederzeit verwendet werden, um den realen Prototyp eines Objekts zu erhalten, ohne auf den Konstruktor zuzugreifen oder die externe Prototypkette aufrechtzuerhalten. Wie im vorherigen Abschnitt erwähnt, können wir es daher wie folgt umschreiben:
Die Codekopie lautet wie folgt:
Funktion getattrfromobj (attr, obj) {
if (typeof (obj) === "Objekt") {
Tun {
var Proto = Object.getPrototypeof (Hund);
if (proto [attr]) {
Return Proto [Attr];
}
}
während (Proto);
}
und definierte zurückkehren;
}
Natürlich kann diese Methode nur in Browsern verwendet werden, die ES5 unterstützen. Um die Rückwärtskompatibilität zu sein, müssen wir die vorherige Methode weiterhin berücksichtigen. Eine geeignetere Methode besteht darin, diese beiden Methoden zu integrieren und zu verkapulieren. Ich glaube, die Leser sind sehr gut darin, also werde ich hier keine Hässlichkeit sein.