Ich weiß, dass diese Antwort 3 Jahre zu spät ist, aber ich denke wirklich, dass die aktuellen Antworten nicht genügend Informationen darüber liefern, wie die prototypische Vererbung besser ist als die klassische Vererbung .
Schauen wir uns zunächst die häufigsten Argumente an, die JavaScript-Programmierer zur Verteidigung der prototypischen Vererbung angeben (ich nehme diese Argumente aus dem aktuellen Antwortpool):
- Es ist einfach.
- Es ist mächtig.
- Dies führt zu kleinerem, weniger redundantem Code.
- Es ist dynamisch und daher besser für dynamische Sprachen.
Jetzt sind alle diese Argumente gültig, aber niemand hat sich die Mühe gemacht, zu erklären, warum. Es ist, als würde man einem Kind sagen, dass es wichtig ist, Mathematik zu lernen. Sicher ist es das, aber das Kind kümmert sich bestimmt nicht darum; und man kann ein Kind nicht wie Mathe machen, wenn man sagt, dass es wichtig ist.
Ich denke, das Problem mit der prototypischen Vererbung ist, dass sie aus der Perspektive von JavaScript erklärt wird. Ich liebe JavaScript, aber die prototypische Vererbung in JavaScript ist falsch. Im Gegensatz zur klassischen Vererbung gibt es zwei Muster der prototypischen Vererbung:
- Das prototypische Muster der prototypischen Vererbung.
- Das Konstruktormuster der prototypischen Vererbung.
Leider verwendet JavaScript das Konstruktormuster der prototypischen Vererbung. Dies liegt daran, dass Brendan Eich (der Schöpfer von JS) bei der Erstellung von JavaScript wollte, dass es wie Java aussieht (das klassische Vererbung hat):
Und wir haben es als kleiner Bruder von Java vorangetrieben, da eine ergänzende Sprache wie Visual Basic zu dieser Zeit in den Sprachfamilien von Microsoft zu C ++ gehörte.
Dies ist schlecht, da Benutzer bei der Verwendung von Konstruktoren in JavaScript an Konstruktoren denken, die von anderen Konstruktoren erben. Das ist falsch. Bei der prototypischen Vererbung erben Objekte von anderen Objekten. Konstruktoren kommen nie ins Bild. Das ist es, was die meisten Menschen verwirrt.
Menschen aus Sprachen wie Java, die klassische Vererbung haben, werden noch verwirrter, weil Konstruktoren zwar wie Klassen aussehen, sich aber nicht wie Klassen verhalten. Wie Douglas Crockford feststellte:
Diese Indirektion sollte die Sprache klassisch ausgebildeten Programmierern vertrauter erscheinen lassen, hat dies jedoch nicht getan, wie aus der sehr geringen Meinung der Java-Programmierer zu JavaScript hervorgeht. Das Konstruktormuster von JavaScript hat die klassische Masse nicht angesprochen. Es verdeckte auch die wahre prototypische Natur von JavaScript. Infolgedessen gibt es nur sehr wenige Programmierer, die wissen, wie man die Sprache effektiv einsetzt.
Hier hast du es. Direkt aus dem Maul des Pferdes.
Echte prototypische Vererbung
Bei der prototypischen Vererbung dreht sich alles um Objekte. Objekte erben Eigenschaften von anderen Objekten. Das ist alles dazu. Es gibt zwei Möglichkeiten, Objekte mithilfe der prototypischen Vererbung zu erstellen:
- Erstellen Sie ein brandneues Objekt.
- Klonen Sie ein vorhandenes Objekt und erweitern Sie es.
Hinweis: JavaScript bietet zwei Möglichkeiten zum Klonen eines Objekts: Delegierung und Verkettung . Von nun an verwende ich das Wort "Klon", um sich ausschließlich auf die Vererbung durch Delegierung zu beziehen, und das Wort "Kopieren", um sich ausschließlich auf die Vererbung durch Verkettung zu beziehen.
Genug Gerede. Sehen wir uns einige Beispiele an. Angenommen, ich habe einen Radiuskreis 5
:
var circle = {
radius: 5
};
Wir können die Fläche und den Umfang des Kreises aus seinem Radius berechnen:
circle.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
circle.circumference = function () {
return 2 * Math.PI * this.radius;
};
Jetzt möchte ich einen weiteren Radiuskreis erstellen 10
. Ein Weg, dies zu tun, wäre:
var circle2 = {
radius: 10,
area: circle.area,
circumference: circle.circumference
};
JavaScript bietet jedoch einen besseren Weg - die Delegierung . Die Object.create
Funktion wird dazu verwendet:
var circle2 = Object.create(circle);
circle2.radius = 10;
Das ist alles. Sie haben gerade eine prototypische Vererbung in JavaScript durchgeführt. War das nicht einfach? Sie nehmen ein Objekt, klonen es, ändern alles, was Sie brauchen, und hey presto - Sie haben sich ein brandneues Objekt besorgt.
Nun könnten Sie fragen: "Wie ist das einfach? Jedes Mal, wenn ich einen neuen Kreis erstellen möchte, muss ich ihn klonen circle
und ihm manuell einen Radius zuweisen." Nun, die Lösung besteht darin, eine Funktion zu verwenden, um das schwere Heben für Sie zu erledigen:
function createCircle(radius) {
var newCircle = Object.create(circle);
newCircle.radius = radius;
return newCircle;
}
var circle2 = createCircle(10);
Tatsächlich können Sie all dies wie folgt zu einem einzigen Objektliteral kombinieren:
var circle = {
radius: 5,
create: function (radius) {
var circle = Object.create(this);
circle.radius = radius;
return circle;
},
area: function () {
var radius = this.radius;
return Math.PI * radius * radius;
},
circumference: function () {
return 2 * Math.PI * this.radius;
}
};
var circle2 = circle.create(10);
Prototypische Vererbung in JavaScript
Wenn Sie im obigen Programm feststellen, dass die create
Funktion einen Klon von erstellt circle
, ihm einen neuen zuweist radius
und ihn dann zurückgibt. Genau das macht ein Konstruktor in JavaScript:
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
Circle.prototype.circumference = function () {
return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);
Das Konstruktormuster in JavaScript ist das invertierte prototypische Muster. Anstatt ein Objekt zu erstellen, erstellen Sie einen Konstruktor. Das new
Schlüsselwort bindet den this
Zeiger im Konstruktor an einen Klon des prototype
Konstruktors.
Klingt verwirrend? Dies liegt daran, dass das Konstruktormuster in JavaScript die Dinge unnötig kompliziert. Dies ist für die meisten Programmierer schwer zu verstehen.
Anstatt an Objekte zu denken, die von anderen Objekten erben, denken sie an Konstruktoren, die von anderen Konstruktoren erben, und werden dann völlig verwirrt.
Es gibt eine ganze Reihe anderer Gründe, warum das Konstruktormuster in JavaScript vermieden werden sollte. Sie können darüber in meinem Blog-Beitrag hier lesen: Konstruktoren gegen Prototypen
Was sind die Vorteile der prototypischen Vererbung gegenüber der klassischen Vererbung? Lassen Sie uns noch einmal die häufigsten Argumente durchgehen und erklären, warum .
1. Prototypische Vererbung ist einfach
CMS gibt in seiner Antwort an:
Meiner Meinung nach ist der Hauptvorteil der prototypischen Vererbung ihre Einfachheit.
Lassen Sie uns überlegen, was wir gerade getan haben. Wir haben ein Objekt circle
mit einem Radius von erstellt 5
. Dann haben wir es geklont und dem Klon einen Radius von gegeben 10
.
Daher brauchen wir nur zwei Dinge, damit die prototypische Vererbung funktioniert:
- Eine Möglichkeit, ein neues Objekt zu erstellen (z. B. Objektliterale).
- Eine Möglichkeit, ein vorhandenes Objekt zu erweitern (z
Object.create
. B. ).
Im Gegensatz dazu ist die klassische Vererbung viel komplizierter. In der klassischen Vererbung haben Sie:
- Klassen.
- Objekt.
- Schnittstellen.
- Abstrakte Klassen.
- Abschlussklassen.
- Virtuelle Basisklassen.
- Konstruktoren.
- Zerstörer.
Du hast die Idee. Der Punkt ist, dass die prototypische Vererbung leichter zu verstehen, leichter zu implementieren und leichter zu überlegen ist.
Wie Steve Yegge es in seinem klassischen Blog-Beitrag " Portrait of a N00b " ausdrückt :
Metadaten sind jede Art von Beschreibung oder Modell von etwas anderem. Die Kommentare in Ihrem Code sind nur eine Beschreibung der Berechnung in natürlicher Sprache. Was Metadaten-Metadaten ausmacht, ist, dass dies nicht unbedingt erforderlich ist. Wenn ich einen Hund mit Stammbaumpapieren habe und den Papierkram verliere, habe ich immer noch einen vollkommen gültigen Hund.
Im gleichen Sinne sind Klassen nur Metadaten. Klassen sind für die Vererbung nicht unbedingt erforderlich. Einige Leute (normalerweise n00bs) finden es jedoch angenehmer, mit Klassen zu arbeiten. Es gibt ihnen ein falsches Gefühl der Sicherheit.
Wir wissen auch, dass statische Typen nur Metadaten sind. Sie sind eine spezielle Art von Kommentar, der sich an zwei Arten von Lesern richtet: Programmierer und Compiler. Statische Typen erzählen eine Geschichte über die Berechnung, vermutlich um beiden Lesergruppen zu helfen, die Absicht des Programms zu verstehen. Die statischen Typen können jedoch zur Laufzeit weggeworfen werden, da es sich letztendlich nur um stilisierte Kommentare handelt. Sie sind wie Stammbaumpapiere: Es könnte einen bestimmten unsicheren Persönlichkeitstyp über ihren Hund glücklicher machen, aber dem Hund ist das sicher egal.
Wie ich bereits sagte, geben Klassen den Menschen ein falsches Gefühl der Sicherheit. Zum Beispiel erhalten Sie NullPointerException
in Java zu viele s, selbst wenn Ihr Code perfekt lesbar ist. Ich finde, dass klassische Vererbung normalerweise die Programmierung behindert, aber vielleicht ist das nur Java. Python hat ein erstaunliches klassisches Vererbungssystem.
2. Prototypische Vererbung ist mächtig
Die meisten Programmierer mit klassischem Hintergrund argumentieren, dass die klassische Vererbung leistungsfähiger ist als die prototypische Vererbung, weil sie:
- Private Variablen.
- Mehrfachvererbung.
Diese Behauptung ist falsch. Wir wissen bereits, dass JavaScript private Variablen über Schließungen unterstützt , aber was ist mit Mehrfachvererbung? Objekte in JavaScript haben nur einen Prototyp.
Die Wahrheit ist, dass die Vererbung von Prototypen die Vererbung von mehreren Prototypen unterstützt. Prototypische Vererbung bedeutet einfach, dass ein Objekt von einem anderen Objekt erbt. Es gibt tatsächlich zwei Möglichkeiten, die prototypische Vererbung zu implementieren :
- Delegation oder differenzielle Vererbung
- Klonen oder verkettete Vererbung
Ja, mit JavaScript können Objekte nur an ein anderes Objekt delegiert werden. Sie können jedoch die Eigenschaften einer beliebigen Anzahl von Objekten kopieren. Zum Beispiel _.extend
macht genau das.
Natürlich betrachten viele Programmierer dies nicht als echte Vererbung, weil instanceof
und isPrototypeOf
sagen etwas anderes. Dies kann jedoch leicht behoben werden, indem auf jedem Objekt, das durch Verkettung von einem Prototyp erbt, eine Reihe von Prototypen gespeichert werden:
function copyOf(object, prototype) {
var prototypes = object.prototypes;
var prototypeOf = Object.isPrototypeOf;
return prototypes.indexOf(prototype) >= 0 ||
prototypes.some(prototypeOf, prototype);
}
Daher ist die prototypische Vererbung genauso mächtig wie die klassische Vererbung. Tatsächlich ist es viel leistungsfähiger als die klassische Vererbung, da Sie bei der prototypischen Vererbung von Hand auswählen können, welche Eigenschaften kopiert und welche von verschiedenen Prototypen weggelassen werden sollen.
Bei der klassischen Vererbung ist es unmöglich (oder zumindest sehr schwierig) zu wählen, welche Eigenschaften Sie erben möchten. Sie verwenden virtuelle Basisklassen und Schnittstellen, um das Diamantproblem zu lösen .
In JavaScript werden Sie jedoch höchstwahrscheinlich nie von dem Diamantproblem hören, da Sie genau steuern können, welche Eigenschaften Sie erben möchten und von welchen Prototypen.
3. Prototypische Vererbung ist weniger redundant
Dieser Punkt ist etwas schwieriger zu erklären, da die klassische Vererbung nicht unbedingt zu redundanterem Code führt. Tatsächlich wird die Vererbung, ob klassisch oder prototypisch, verwendet, um die Redundanz im Code zu verringern.
Ein Argument könnte sein, dass die meisten Programmiersprachen mit klassischer Vererbung statisch typisiert sind und der Benutzer explizit Typen deklarieren muss (im Gegensatz zu Haskell, das implizite statische Typisierung aufweist). Dies führt daher zu einem ausführlicheren Code.
Java ist für dieses Verhalten berüchtigt. Ich erinnere mich deutlich daran, dass Bob Nystrom in seinem Blogbeitrag über Pratt Parsers die folgende Anekdote erwähnt hat :
Sie müssen Javas Bürokratie "Bitte unterschreiben Sie es in vierfacher Ausfertigung" hier lieben.
Wieder denke ich, das liegt nur daran, dass Java so viel nervt.
Ein gültiges Argument ist, dass nicht alle Sprachen mit klassischer Vererbung Mehrfachvererbung unterstützen. Wieder fällt mir Java ein. Ja, Java hat Schnittstellen, aber das reicht nicht aus. Manchmal braucht man wirklich Mehrfachvererbung.
Da die prototypische Vererbung eine Mehrfachvererbung ermöglicht, ist Code, der eine Mehrfachvererbung erfordert, weniger redundant, wenn er unter Verwendung einer prototypischen Vererbung geschrieben wird, als in einer Sprache, die eine klassische Vererbung, jedoch keine Mehrfachvererbung aufweist.
4. Die prototypische Vererbung ist dynamisch
Einer der wichtigsten Vorteile der prototypischen Vererbung besteht darin, dass Sie Prototypen nach ihrer Erstellung neue Eigenschaften hinzufügen können. Auf diese Weise können Sie einem Prototyp neue Methoden hinzufügen, die automatisch allen Objekten zur Verfügung gestellt werden, die an diesen Prototyp delegieren.
Dies ist bei der klassischen Vererbung nicht möglich, da Sie eine einmal erstellte Klasse zur Laufzeit nicht mehr ändern können. Dies ist wahrscheinlich der größte Vorteil der prototypischen Vererbung gegenüber der klassischen Vererbung, und er hätte an der Spitze stehen müssen. Ich mag es jedoch, das Beste für das Ende zu speichern.
Fazit
Prototypische Vererbung ist wichtig. Es ist wichtig, JavaScript-Programmierer darüber zu informieren, warum das Konstruktormuster der prototypischen Vererbung zugunsten des prototypischen Musters der prototypischen Vererbung aufgegeben werden soll.
Wir müssen anfangen, JavaScript richtig zu unterrichten, und das bedeutet, neuen Programmierern zu zeigen, wie man Code unter Verwendung des prototypischen Musters anstelle des Konstruktormusters schreibt.
Es wird nicht nur einfacher sein, die Vererbung von Prototypen anhand des prototypischen Musters zu erklären, sondern auch bessere Programmierer.
Wenn Ihnen diese Antwort gefallen hat, sollten Sie auch meinen Blog-Beitrag " Warum prototypische Vererbung wichtig ist " lesen . Vertrauen Sie mir, Sie werden nicht enttäuscht sein.