JavaScript ist eine objektorientierte Sprache. In JavaScript gibt es ein sehr klassisches Sprichwort: Alles ist ein Objekt. Da es objektorientiert ist, weist es drei Hauptmerkmale der Objektorientierung auf: Kapselung, Vererbung und Polymorphismus. Worüber wir hier sprechen, ist die JavaScript-Vererbung, und über die anderen beiden werden wir später sprechen.
Die JavaScript-Vererbung unterscheidet sich von der C-Vererbung. Die C-Vererbung basiert auf Klassen, während die JavaScript-Vererbung auf Prototypen basiert.
Jetzt kommt das Problem.
Was ist ein Prototyp? Für den Prototyp können wir auf die Klasse in C verweisen, die auch die Eigenschaften und Methoden des Objekts speichert. Schreiben wir zum Beispiel ein einfaches Objekt
Wir können sehen, dass es sich um ein Objekt „Tier“ handelt, das einen Attributnamen und eine Methode setName hat. Es ist zu beachten, dass alle Instanzen des Objekts diese Methode gemeinsam nutzen, sobald der Prototyp geändert wird, beispielsweise durch das Hinzufügen einer Methode. Zum Beispiel
Derzeit verfügt das Tier nur über das Namensattribut. Wenn wir einen Satz hinzufügen,
Zu diesem Zeitpunkt verfügt animal auch über eine setName-Methode.
Erben Sie diese Kopie – beginnend mit dem leeren Objekt. Wir wissen, dass es unter den Grundtypen von JS ein aufgerufenes Objekt gibt und dessen grundlegendste Instanz das leere Objekt ist, dh die Instanz, die durch den direkten Aufruf eines neuen Objekts generiert wird (), oder Es wird mit dem Literal {} deklariert. Ein leeres Objekt ist ein „sauberes Objekt“ mit nur vordefinierten Eigenschaften und Methoden, und alle anderen Objekte erben vom leeren Objekt, sodass alle Objekte diese vordefinierten Eigenschaften und Methoden haben. Ein Prototyp ist eigentlich eine Objektinstanz. Die Bedeutung von Prototyp ist: Wenn der Konstruktor ein Prototypobjekt A hat, müssen die vom Konstruktor erstellten Instanzen von A kopiert werden. Da die Instanz von Objekt A kopiert wird, muss die Instanz alle Eigenschaften, Methoden und anderen Eigenschaften von A erben. Wie wird also die Replikation erreicht? Methode 1: Kopieren der Konstruktion Bei jeder Erstellung einer Instanz wird eine Instanz vom Prototyp kopiert. Die neue Instanz und der Prototyp belegen denselben Speicherplatz. Obwohl dadurch obj1 und obj2 „völlig konsistent“ mit ihren Prototypen sind, ist es auch sehr unwirtschaftlich – der Speicherplatzverbrauch wird schnell ansteigen. Wie im Bild gezeigt:
Methode 2: Copy-on-Write Diese Strategie beruht auf einem konsistenten Trick des Systems: Copy-on-Write. Ein typisches Beispiel für eine solche Täuschung ist die Dynamic Link Library (DDL) im Betriebssystem, deren Speicherbereich beim Schreiben immer kopiert wird. Wie im Bild gezeigt:
Wir müssen im System nur angeben, dass obj1 und obj2 ihren Prototypen entsprechen, sodass wir beim Lesen nur den Anweisungen zum Lesen der Prototypen folgen müssen. Wenn wir die Eigenschaften eines Objekts (z. B. obj2) schreiben müssen, kopieren wir ein Prototypbild und verweisen zukünftige Operationen auf dieses Bild. Wie im Bild gezeigt:
Der Vorteil dieser Methode besteht darin, dass wir beim Erstellen von Instanzen und beim Lesen von Attributen keinen großen Speicheraufwand benötigen. Beim ersten Schreiben verwenden wir nur einen Teil des Codes, um Speicher zuzuweisen, was einen gewissen Code- und Speicheraufwand mit sich bringt. . Es gibt jedoch keinen solchen Mehraufwand mehr, da der Zugriff auf das Bild genauso effizient ist wie der Zugriff auf den Prototyp. Für Systeme, die häufig Schreibvorgänge ausführen, ist diese Methode jedoch nicht wirtschaftlicher als die vorherige Methode. Methode 3: Lesedurchquerung Diese Methode ändert die Replikationsgranularität vom Prototyp zum Mitglied. Das Merkmal dieser Methode besteht darin, dass nur beim Schreiben eines Mitglieds einer Instanz die Mitgliedsinformationen in das Instanzbild kopiert werden. Beim Schreiben eines Objektattributs, beispielsweise (obj2.value=10), wird ein Attributwert mit dem Namen value generiert und in die Mitgliederliste des obj2-Objekts eingefügt. Schauen Sie sich das Bild an:
Es kann festgestellt werden, dass obj2 immer noch eine Referenz ist, die auf den Prototyp verweist, und während des Vorgangs keine Objektinstanz mit der gleichen Größe wie der Prototyp erstellt wird. Auf diese Weise führen Schreibvorgänge nicht zu großen Speicherzuweisungen, sodass die Speichernutzung sparsamer wird. Der Unterschied besteht darin, dass obj2 (und alle Objektinstanzen) eine Mitgliederliste verwalten müssen. Diese Mitgliederliste folgt zwei Regeln: Beim Lesen wird garantiert zuerst auf sie zugegriffen. Wenn im Objekt kein Attribut angegeben ist, wird versucht, die gesamte Prototypenkette des Objekts zu durchlaufen, bis der Prototyp leer ist oder das Attribut gefunden wird. Die Prototypenkette wird später besprochen. Unter den drei Methoden weist die Lesedurchquerung offensichtlich die beste Leistung auf. Daher ist die prototypische Vererbung von JavaScript die Lesedurchquerung. Konstruktor Personen, die mit C vertraut sind, werden nach dem Lesen des Codes des obersten Objekts definitiv verwirrt sein. Ohne das Schlüsselwort class ist es einfacher zu verstehen. Schließlich gibt es das Schlüsselwort function, das nur ein anderes Schlüsselwort ist. Aber was ist mit den Konstrukteuren? Tatsächlich verfügt auch JavaScript über einen ähnlichen Konstruktor, der jedoch als Konstruktor bezeichnet wird. Bei Verwendung des new-Operators wurde tatsächlich der Konstruktor aufgerufen und dieser an ein Objekt gebunden. Beispielsweise verwenden wir den folgenden Code
Tier wird undefiniert sein. Manche Leute werden sagen, dass kein Rückgabewert natürlich undefiniert ist. Wenn Sie dann die Objektdefinition von Animal ändern:
Raten Sie mal, welches Tier jetzt ist?
Zu diesem Zeitpunkt wird das Tier zu einem Fenster. Der Unterschied besteht darin, dass das Fenster erweitert wird, sodass das Fenster ein Namensattribut hat. Dies liegt daran, dass standardmäßig „window“ verwendet wird, die Variable der obersten Ebene, wenn sie nicht angegeben wird. Nur durch den Aufruf des Schlüsselworts new kann der Konstruktor korrekt aufgerufen werden. Wie kann man also verhindern, dass Benutzer das neue Schlüsselwort verpassen? Wir können einige kleine Änderungen vornehmen:
//Legen Sie __proto__ als integriertes Mitglied der Funktion fest und get_prototyoe() ist ihre Methode
Es gibt verschiedene Arten der JS-Vererbung, hier sind zwei Arten
1. Methode 1 Diese Methode wird am häufigsten verwendet und bietet eine höhere Sicherheit. Definieren wir zunächst zwei Objekte
für Hund verwenden
Lassen Sie uns noch einmal einen Blick darauf werfen:
fertig. Diese Methode ist Teil der Wartung der Prototypenkette und wird im Folgenden ausführlich erläutert. 2. Methode 2 Diese Methode hat ihre Vor- und Nachteile, aber die Nachteile überwiegen die Vorteile. Schauen wir uns zuerst den Code an
function Animal(name) {
This.name = name;
}
Animal.prototype.setName = function(name) {
This.name = name;
}
Funktion Hund(Alter) {
This.age = Alter;
}
Dog.prototype = Animal.prototype;
Dadurch wird das Kopieren von Prototypen erreicht.
Der Vorteil dieser Methode besteht darin, dass keine Objekte instanziiert werden müssen (im Vergleich zu Methode 1), was Ressourcen spart. Die Nachteile liegen ebenfalls auf der Hand. Zusätzlich zu dem gleichen Problem wie oben, das heißt, dass der Konstruktor auf das übergeordnete Objekt verweist, kann er nur die vom Prototyp des übergeordneten Objekts deklarierten Eigenschaften und Methoden kopieren. Mit anderen Worten, im obigen Code kann das Namensattribut des Tierobjekts nicht kopiert werden, aber die setName-Methode kann kopiert werden. Das Schlimmste ist, dass sich jede Änderung am Prototyp des untergeordneten Objekts auf den Prototyp des übergeordneten Objekts auswirkt, dh die von beiden Objekten deklarierten Instanzen sind betroffen. Daher wird diese Methode nicht empfohlen.
Prototypenkette
Jeder, der Vererbung geschrieben hat, weiß, dass Vererbung auf mehreren Ebenen erfolgen kann. In JS stellt dies die Prototypenkette dar. Die Prototypenkette wurde oben schon oft erwähnt. Was ist also die Prototypenkette? Eine Instanz sollte mindestens ein Proto-Attribut haben, das auf den Prototyp verweist, der die Grundlage des Objektsystems in JavaScript bildet. Diese Eigenschaft ist jedoch unsichtbar. Wir nennen sie die „interne Prototypenkette“, um sie von der „Konstruktor-Prototypenkette“ zu unterscheiden, die aus dem Prototyp des Konstruktors besteht (was wir normalerweise als „Prototypenkette“ bezeichnen). Konstruieren wir zunächst eine einfache Vererbungsbeziehung gemäß dem obigen Code:
Zur Erinnerung: Wie bereits erwähnt, erben alle Objekte leere Objekte. Deshalb haben wir eine Prototypenkette aufgebaut:
Wir können sehen, dass der Prototyp des untergeordneten Objekts auf die Instanz des übergeordneten Objekts verweist und so die Konstruktor-Prototypkette bildet. Das interne Protoobjekt der untergeordneten Instanz verweist auch auf die Instanz des übergeordneten Objekts und bildet so eine interne Prototypenkette. Wenn wir ein bestimmtes Attribut finden müssen, ähnelt der Code
Dans cet exemple, si nous recherchons l'attribut name dans dog, il sera recherché dans la liste des membres dans dog. Bien sûr, il ne sera pas trouvé car la liste des membres de dog n'a désormais que l'âge. Ensuite, il continuera à chercher le long de la chaîne de prototypes, c'est-à-dire l'instance pointée par .proto, c'est-à-dire qu'en animal, il trouvera l'attribut name et le renverra. S'il recherche une propriété qui n'existe pas et ne peut pas la trouver dans animal, il continuera à chercher avec .proto et trouvera un objet vide. S'il ne le trouve pas, il continuera à chercher avec .proto et avec. objet vide. proto pointe vers null, cherchant la sortie.
Maintenance de la chaîne de prototypes Nous avons soulevé une question lorsque nous venons de parler de l'héritage prototypique. Lors de l'utilisation de la méthode 1 pour construire l'héritage, le constructeur de l'instance de l'objet enfant pointe vers l'objet parent. L'avantage est que nous pouvons accéder à la chaîne de prototypes via l'attribut constructeur, mais les inconvénients sont également évidents. Un objet, l'instance qu'il génère doit pointer vers lui-même, c'est-à-dire
Ensuite, lorsqu'on surcharge la propriété prototype, le constructeur de l'instance générée par le sous-objet ne pointe pas sur lui-même ! Cela va à l’encontre de l’intention initiale du constructeur. Nous avons évoqué une solution ci-dessus :
On dirait qu’il n’y a pas de problème. Mais en fait, cela pose un nouveau problème, car nous constaterons que nous ne pouvons pas revenir en arrière sur la chaîne de prototypes, car nous ne pouvons pas trouver l'objet parent et que la propriété .proto de la chaîne de prototypes interne est inaccessible. Par conséquent, SpiderMonkey propose une solution améliorée : ajouter un attribut nommé __proto__ à tout objet créé, qui pointe toujours vers le prototype utilisé par le constructeur. De cette façon, toute modification du constructeur n’affectera pas la valeur de __proto__, ce qui facilite la maintenance du constructeur.
Cependant, il y a deux autres problèmes :
__proto__ est remplaçable, ce qui signifie qu'il y a toujours des risques lors de son utilisation
__proto__ est un traitement spécial de spiderMonkey et ne peut pas être utilisé dans d'autres moteurs (tels que JScript).
Nous avons également un autre moyen, qui consiste à conserver les propriétés du constructeur du prototype et à initialiser les propriétés du constructeur de l'instance dans la fonction constructeur de la sous-classe.
Le code est le suivant : Réécrire le sous-objet
De cette façon, les constructeurs de toutes les instances d'objets enfants pointent correctement vers l'objet et le constructeur du prototype pointe vers l'objet parent. Bien que cette méthode soit relativement inefficace car l’attribut constructeur doit être réécrit à chaque fois qu’une instance est construite, il ne fait aucun doute que cette méthode peut résoudre efficacement la contradiction précédente. ES5 prend cette situation en considération et résout complètement ce problème : vous pouvez utiliser Object.getPrototypeOf() à tout moment pour obtenir le vrai prototype d'un objet sans avoir à accéder au constructeur ou à maintenir une chaîne de prototypes externe. Par conséquent, pour trouver les propriétés de l'objet comme mentionné dans la section précédente, nous pouvons le réécrire comme suit :
Bien entendu, cette méthode ne peut être utilisée que dans les navigateurs prenant en charge ES5. Pour des raisons de compatibilité ascendante, nous devons toujours considérer la méthode précédente. Une méthode plus appropriée consiste à intégrer et à encapsuler ces deux méthodes. Je pense que les lecteurs sont très bons dans ce domaine, je ne vais donc pas me montrer ici.