Im wahren Sinne ist JavaScript keine objektorientierte Sprache und bietet keine traditionellen Vererbungsmethoden, sondern bietet eine Methode zur Prototyperbe und unter Verwendung der Prototypeigenschaften, die es zur Verfügung stellt, um die Vererbung zu erreichen.
Prototyp und Prototypkette
Bevor wir über Prototypereritanz sprechen, sollten wir zuerst über Prototypen und Prototypketten sprechen. Immerhin ist dies die Grundlage für die Realisierung der Prototyperbanz.
In JavaScript hat jede Funktion einen Prototyp -Attributprototyp, der auf ihren eigenen Prototyp hinweist, und das von dieser Funktion erstellte Objekt hat auch ein __Proto__ -Attribut, das auf diesen Prototyp hinweist. Der Prototyp der Funktion ist ein Objekt, sodass dieses Objekt auch einen __Proto__ hat, der auf seinen eigenen Prototyp hinweist, so dass es für Schicht tiefer geht, bis der Prototyp des Objektobjekts eine Prototypkette bildet. Die folgende Abbildung erklärt die Beziehung zwischen Prototypen und Prototypketten in JavaScript sehr gut.
Jede Funktion ist ein Objekt, das von der Funktionsfunktion erstellt wurde. Jede Funktion hat auch ein __Proto__ -Attribut, das auf den Prototyp der Funktionsfunktion zeigt. Es sollte hier darauf hingewiesen werden, dass das, was die Prototypkette wirklich bildet, das __Proto__ -Attribut jedes Objekts ist, nicht das Prototyp -Attribut der Funktion, was sehr wichtig ist.
Prototyp -Vererbung
Grundmodus
Die Codekopie lautet wie folgt:
var parent = function () {
this.name = 'Eltern';
};
Parent.prototype.getname = function () {
kehre diesen namennamen zurück;
};
Parent.prototype.obj = {a: 1};
var child = function () {
this.name = 'Kind';
};
Child.Prototype = new Parent ();
var parent = new Parent ();
var Child = neues Kind ();
console.log (parent.getName ()); //Elternteil
console.log (child.getName ()); //Kind
Dies ist der einfachste Weg, um den Prototyp -Vererbung zu implementieren und dem Objekt der übergeordneten Klasse dem Prototyp des Unterklassenkonstruktors direkt zuzuweisen, sodass die Objekte der Unterklasse auf die Eigenschaften in der übergeordneten Klasse und den Prototyp des übergeordneten Klassenkonstruktors zugreifen können. Das Prototyp -Vererbungsdiagramm dieser Methode lautet wie folgt:
Die Vorteile dieser Methode sind offensichtlich, die Implementierung ist sehr einfach und erfordert keine besonderen Vorgänge. Die Nachteile sind ebenfalls offensichtlich. Wenn die Unterklasse dieselben Initialisierungsaktionen wie im Konstruktor der Elternklassen ausführen muss, müssen Sie die Operationen in der übergeordneten Klasse im Subklass -Konstruktor wiederholen:
Die Codekopie lautet wie folgt:
var parent = function (name) {
this.name = name || 'Elternteil';
};
Parent.prototype.getname = function () {
kehre diesen namennamen zurück;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
this.name = name || 'Kind' ;
};
Child.Prototype = new Parent ();
var Eltern = neuer Elternteil ('MyParent');
var Child = neues Kind ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); //Mein Kind
In der obigen Situation muss nur das Namensattribut initialisiert werden. Wenn die Initialisierungsarbeit weiter zunimmt, ist diese Methode sehr unpraktisch. Daher gibt es eine Möglichkeit, Folgendes zu verbessern.
Konstruktor ausleihen
Die Codekopie lautet wie folgt:
var parent = function (name) {
this.name = name || 'Elternteil';
};
Parent.prototype.getname = function () {
kehre diesen namennamen zurück;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Elternteil.Apply (this, Argumente);
};
Child.Prototype = new Parent ();
var Eltern = neuer Elternteil ('MyParent');
var Child = neues Kind ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); //Mein Kind
Die obige Methode führt dieselbe Initialisierungsarbeit aus, indem der Aufruf auf den Konstruktor der übergeordneten Klassen im Unterklassenkonstruktor angewendet wird, sodass die Unterklasse unabhängig davon, wie viel Initialisierungsarbeit in der übergeordneten Klasse erfolgt, dieselbe Initialisierungsarbeit ausführen kann. Es gibt jedoch ein weiteres Problem mit der obigen Implementierung. Der Konstruktor der übergeordneten Klassen wurde zweimal, einmal im Subklass -Konstruktor und einmal im Subklass -Prototyp ausgeführt, dies ist eine Menge redundant, daher müssen wir eine Verbesserung vornehmen:
Die Codekopie lautet wie folgt:
var parent = function (name) {
this.name = name || 'Elternteil';
};
Parent.prototype.getname = function () {
kehre diesen namennamen zurück;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Elternteil.Apply (this, Argumente);
};
Child.Prototype = parent.prototype;
var Eltern = neuer Elternteil ('MyParent');
var Child = neues Kind ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); //Mein Kind
Auf diese Weise müssen wir den Konstruktor der übergeordneten Klassen nur einmal im Subklassenkonstruktor ausführen, und gleichzeitig können wir die Eigenschaften im Prototyp der Elternklassen erben. Dies entspricht eher der ursprünglichen Absicht des Prototyps, nämlich den Inhalt zu setzen, der im Prototyp wiederverwendet werden muss, und wir erben nur den wiederverwendbaren Inhalt im Prototyp. Das Prototyp -Diagramm der obigen Methode lautet wie folgt:
Temporärer Konstruktormodus (Holy Grail -Modus)
Es gibt immer noch ein Problem mit der Version, die das obige Konstruktormuster ausgeliehen hat. Es weist den Prototyp der übergeordneten Klasse dem Prototyp der Unterklasse direkt zu, was ein Problem verursacht. Wenn der Prototyp der Unterklasse geändert wird, wirkt sich die Änderung auch den Prototyp der übergeordneten Klasse aus und beeinflusst dann das übergeordnete Klassenobjekt. Dies ist definitiv nicht das, was jeder sehen will. Um dieses Problem zu lösen, ist ein temporäres Konstruktormuster verfügbar.
Die Codekopie lautet wie folgt:
var parent = function (name) {
this.name = name || 'Elternteil';
};
Parent.prototype.getname = function () {
kehre diesen namennamen zurück;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Elternteil.Apply (this, Argumente);
};
var f = new Function () {};
F.Prototype = parent.prototype;
Child.Prototype = new f ();
var Eltern = neuer Elternteil ('MyParent');
var Child = neues Kind ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); //Mein Kind
Das Prototyp -Vererbungsdiagramm dieser Methode lautet wie folgt:
Es ist leicht zu erkennen, dass durch Hinzufügen eines temporären Konstruktors F zwischen dem Prototyp der übergeordneten Klassen und dem Prototyp der untergeordneten Klasse die Verbindung zwischen dem Prototyp der untergeordneten Klassen und dem Prototyp der Elternklassen abgeschnitten ist, so dass der Prototyp der Elternklassen nicht beeinträchtigt wird, wenn der Prototyp der untergeordneten Klassenmodifizierung modifiziert wird.
Meine Methode
Der Heilige Gral -Modus endet im "JavaScript -Modus", aber egal welche Methode oben ist, es gibt ein Problem, das nicht leicht entdeckt werden kann. Sie können sehen, dass ich der Prototyp -Eigenschaft von 'Eltern' ein OBJ -Objektliteralattribut hinzugefügt habe, aber es war nie nützlich. Schauen wir uns die folgende Situation an, die auf dem Heiligen Gral -Modus basiert:
Die Codekopie lautet wie folgt:
var parent = function (name) {
this.name = name || 'Elternteil';
};
Parent.prototype.getname = function () {
kehre diesen namennamen zurück;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Elternteil.Apply (this, Argumente);
};
var f = new Function () {};
F.Prototype = parent.prototype;
Child.Prototype = new f ();
var Eltern = neuer Elternteil ('MyParent');
var Child = neues Kind ('mychild');
console.log (Child.obj.a); // 1
console.log (parent.obj.a); // 1
Child.obj.a = 2;
console.log (Child.obj.a); // 2
console.log (parent.obj.a); // 2
Wenn ich das obj.A -Objekt im obigen Fall modifiziere, wird der Obj.A im Prototyp der Elternklasse ebenfalls geändert, was das gleiche Problem wie der gemeinsame Prototyp verursacht. Dies geschieht, weil wir beim Zugriff auf child.obj.a der Prototypkette folgen und den Prototyp der übergeordneten Klasse finden, dann das OBJ -Attribut finden und dann OBJ.A. Schauen wir uns die folgende Situation an:
Die Codekopie lautet wie folgt:
var parent = function (name) {
this.name = name || 'Elternteil';
};
Parent.prototype.getname = function () {
kehre diesen namennamen zurück;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Elternteil.Apply (this, Argumente);
};
var f = new Function () {};
F.Prototype = parent.prototype;
Child.Prototype = new f ();
var Eltern = neuer Elternteil ('MyParent');
var Child = neues Kind ('mychild');
console.log (Child.obj.a); // 1
console.log (parent.obj.a); // 1
Child.obj.a = 2;
console.log (Child.obj.a); // 2
console.log (parent.obj.a); // 2
Hier gibt es ein Schlüsselproblem. Wenn ein Objekt im Prototyp auf Eigenschaften zugreift, sind die Eigenschaften im Prototyp schreibgeschützt zum Objekt. Das heißt, das untergeordnete Objekt kann das OBJ -Objekt lesen, aber die OBJ -Objektreferenz im Prototyp kann nicht geändert werden. Daher wird es beim Modifys von Child OBJ den OBJ im Prototyp nicht beeinflussen. Es fügt nur ein OBJ -Attribut zu seinem eigenen Objekt hinzu und überschreibt das OBJ -Attribut im übergeordneten Prototyp. Wenn das untergeordnete Objekt OBJ.A ändert, liest es zunächst den Verweis auf OBJ im Prototyp. Zu diesem Zeitpunkt verweisen Child.obj und Parent.Prototype.obj auf dasselbe Objekt, sodass die Änderung von OBJ.A durch das Kind den Wert von übergeordnetem. Die Vererbungsmethode des $ Scope -Nistens in AngularJS wird durch Modellierung des Prototyps der Vererbung in Javasript implementiert.
Entsprechend der obigen Beschreibung wird die obige Situation der obigen Prototyp der übergeordneten Klassenprototyp auftreten, solange der im Subklassenobjekt zugegriffene Prototyp über die übergeordnete Klassenprototyp entspricht. Daher können wir den Prototyp der übergeordneten Klassen kopieren und ihn dann dem Subklass -Prototyp zuweisen. Auf diese Weise modifiziert die Unterklasse die Eigenschaften im Prototyp nur eine Kopie des Prototyps der übergeordneten Klassen und wirkt sich nicht auf den Prototyp der Elternklassen aus. Die spezifische Implementierung ist wie folgt:
Die Codekopie lautet wie folgt:
var Deepclone = Funktion (Quelle, Ziel) {
Quelle = Quelle || {};
var tostr = Object.Prototype.toString,
arrstr = '[Objektarray]';
für (var i in Quelle) {
if (source.hasownProperty (i)) {
var item = source [i];
if (typeof item === 'Objekt') {
Ziel [i] = (tostr.apply (item) .tolowerCase () === arrstr): []? {};
DeepClone (Element, Ziel [i]);
}anders{
DeepClone (Element, Ziel [i]);
}
}
}
Ziel zurückgeben;
};
var parent = function (name) {
this.name = name || 'Elternteil';
};
Parent.prototype.getname = function () {
kehre diesen namennamen zurück;
};
Parent.prototype.obj = {a: '1'};
var child = function (name) {
Elternteil.Apply (this, Argumente);
};
Child.Prototype = DeepClone (parent.prototype);
var Child = neues Kind ('Kind');
var parent = new übergeordnet ('Eltern');
console.log (Child.obj.a); // 1
console.log (parent.obj.a); // 1
Child.obj.a = '2';
console.log (Child.obj.a); // 2
console.log (parent.obj.a); // 1
Basierend auf allen oben genannten Überlegungen ist die spezifische Implementierung der JavaScript -Vererbung wie folgt. Nur wenn Kind und Eltern Funktionen sind:
Die Codekopie lautet wie folgt:
var Deepclone = Funktion (Quelle, Ziel) {
Quelle = Quelle || {};
var tostr = Object.Prototype.toString,
arrstr = '[Objektarray]';
für (var i in Quelle) {
if (source.hasownProperty (i)) {
var item = source [i];
if (typeof item === 'Objekt') {
Ziel [i] = (tostr.apply (item) .tolowerCase () === arrstr): []? {};
DeepClone (Element, Ziel [i]);
}anders{
DeepClone (Element, Ziel [i]);
}
}
}
Ziel zurückgeben;
};
var extend = function (übergeordnet, untergeordnet) {
Kind = Kind || function () {};
if (parent === undefiniert)
Kind zurückkehren;
// Ausleihen Sie den Konstruktor der Elternklasse aus
Child = function () {
Parent.apply (this, argument);
};
// Erben Sie den Prototyp der übergeordneten Klassen durch Deep Copy
Child.Prototype = DeepClone (parent.prototype);
// Konstruktorattribut zurücksetzen
Child.Prototype.Constructor = Child;
};
Zusammenfassen
In der Tat ist die Implementierung der Vererbung in JavaScript sehr flexibel und vielfältig, und es gibt keine beste Methode. Verschiedene Vererbungsmethoden müssen nach unterschiedlichen Bedürfnissen implementiert werden. Das Wichtigste ist, das Prinzip der Implementierung der Vererbung in JavaScript zu verstehen, dh das Problem von Prototypen und Prototypketten. Solange Sie diese verstehen, können Sie die Vererbung problemlos selbst implementieren.