Heim > Web-Frontend > js-Tutorial > Ausführliche Erläuterung der prototypischen Vererbung in den JavaScript_Javascript-Tipps

Ausführliche Erläuterung der prototypischen Vererbung in den JavaScript_Javascript-Tipps

WBOY
Freigeben: 2016-05-16 16:14:05
Original
1099 Leute haben es durchsucht

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

Code kopieren Der Code lautet wie folgt:

Funktion Tier(Name) {
This.name = name;
}
Animal.prototype.setName = function(name) {
This.name = name;
}
var animal = new Animal("wangwang");

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

Code kopieren Der Code lautet wie folgt:

Funktion Tier(Name) {
This.name = name;
}
var animal = new Animal("wangwang");

Derzeit verfügt das Tier nur über das Namensattribut. Wenn wir einen Satz hinzufügen,

Code kopieren Der Code lautet wie folgt:

Animal.prototype.setName = function(name) {
This.name = name;
}

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

Code kopieren Der Code lautet wie folgt:

var animal = Animal("wangwang");

Tier wird undefiniert sein. Manche Leute werden sagen, dass kein Rückgabewert natürlich undefiniert ist. Wenn Sie dann die Objektdefinition von Animal ändern:

Code kopieren Der Code lautet wie folgt:

Funktion Tier(Name) {
This.name = name;
Geben Sie dies zurück;
}

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:

Code kopieren Der Code lautet wie folgt:

Funktion Tier(Name) {
If(!(diese Instanz von Animal)) {
          neues Tier(Name) zurückgeben; }
This.name = name;
}

Auf diese Weise sind Sie narrensicher. Der Konstruktor wird auch verwendet, um anzugeben, zu welchem ​​Objekt die Instanz gehört. Wir können Instanzen zur Beurteilung verwenden, aber Instanzen geben während der Vererbung sowohl für Vorfahrenobjekte als auch für reale Objekte „true“ zurück und sind daher nicht geeignet. Wenn der Konstruktor mit new aufgerufen wird, zeigt er standardmäßig auf das aktuelle Objekt.

Code kopieren Der Code lautet wie folgt:
console.log(Animal.prototype.constructor === Animal); // true

Wir können anders denken: Der Prototyp hat überhaupt keinen Wert, wenn die Funktion initialisiert wird. Die Implementierung kann die folgende Logik sein

//Legen Sie __proto__ als integriertes Mitglied der Funktion fest und get_prototyoe() ist ihre Methode

Code kopieren Der Code lautet wie folgt:
var __proto__ = null;
Funktion get_prototype() {
If(!__proto__) {
​​​​ __proto__ = new Object();
         __proto__.constructor = this;
}
Geben Sie __proto__;
zurück }


Der Vorteil davon besteht darin, dass nicht jedes Mal, wenn eine Funktion deklariert wird, eine Objektinstanz erstellt wird, was den Aufwand spart. Der Konstruktor kann geändert werden, worauf später noch eingegangen wird. Prototypbasierte Vererbung Ich glaube, jeder weiß fast, was Vererbung ist, deshalb werde ich nicht mit der Untergrenze des IQ prahlen.

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

Code kopieren Der Code lautet wie folgt:
Funktion Tier(Name) {
This.name = name;
}
Funktion Hund(Alter) {
This.age = Alter;
}
var dog = neuer Hund(2);

Um die Vererbung zu konstruieren, ist es sehr einfach, den Prototyp des untergeordneten Objekts auf die Instanz des übergeordneten Objekts zu verweisen (beachten Sie, dass es sich um eine Instanz und nicht um ein Objekt handelt)

Code kopieren Der Code lautet wie folgt:
Dog.prototype = neues Tier("wangwang");

Zu diesem Zeitpunkt hat der Hund zwei Attribute: Name und Alter. Und wenn Sie den Instanzoperator

für Hund verwenden

Code kopieren Der Code lautet wie folgt:
console.log(Hund-Instanz von Animal); // true
console.log(Dog-Instanz von Dog); // false

Auf diese Weise wird eine Vererbung erreicht, es gibt jedoch ein kleines Problem

Code kopieren Der Code lautet wie folgt:
console.log(Dog.prototype.constructor === Animal); // true
console.log(Dog.prototype.constructor === Dog); // false

Sie können sehen, dass sich das Objekt, auf das der Konstruktor zeigt, geändert hat, was nicht unserem Zweck entspricht. Wir können nicht beurteilen, wem unsere neue Instanz gehört. Daher können wir einen Satz hinzufügen:

Code kopieren Der Code lautet wie folgt:
Dog.prototype.constructor = Hund;

Lassen Sie uns noch einmal einen Blick darauf werfen:

Code kopieren Der Code lautet wie folgt:

console.log(Hund-Instanz von Animal); // false
console.log(dog-Instanz von Dog); // true

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

Code kopieren Der Code lautet wie folgt:

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:

Code kopieren Der Code lautet wie folgt:

Funktion Tier(Name) {
This.name = name;
}
Funktion Hund(Alter) {
This.age = Alter;
}
var animal = new Animal("wangwang");
Dog.prototype = animal;
var dog = neuer Hund(2);

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

Code kopieren Der Code lautet wie folgt:

Funktion getAttrFromObj(attr, obj) {
If(typeof(obj) === "object") {
        var proto = obj;
​​​​while(proto) {
If(proto.hasOwnProperty(attr)) {
                     return proto[attr];
            }
              proto = proto.__proto__;
}
}
Rückgabe undefiniert;
}

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

Copier le code Le code est le suivant :

(nouveau obj()).prototype.constructor === obj

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 :

Copier le code Le code est le suivant :

Chien.prototype = new Animal("wangwang");
Dog.prototype.constructor = Chien;

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

Copier le code Le code est le suivant :

fonction Chien(âge) {
This.constructor = arguments.callee;
Cet.age = âge;
>
Chien.prototype = new Animal("wangwang");

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 :

Copier le code Le code est le suivant :

fonction getAttrFromObj(attr, obj) {
Si(typeof(obj) === "objet") {
faire {
          var proto = Object.getPrototypeOf(dog);
Si(proto[attr]) {
                     return proto[attr];
            }
>
​​​​pendant que(proto);
>
Retour indéfini ;
>

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.

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage