Heim >Web-Frontend >js-Tutorial >Muster für die Objektvererbung in JavaScript ES2015
Mit der lang erwarteten Ankunft von ES2015 (früher als ES6 bekannt) ist JavaScript mit Syntax speziell zur Definition von Klassen ausgestattet. In diesem Artikel werde ich untersuchen, ob wir die Klassensyntax nutzen können, um Klassen aus kleineren Teilen zu komponieren.
Die Hierarchie -Tiefe auf ein Minimum hält, ist wichtig, um Ihren Code sauber zu halten. Wenn Sie klug darüber sind, wie Sie Klassen teilen, hilft. Für eine große Codebasis besteht eine Möglichkeit darin, Klassen aus kleineren Teilen zu erstellen. Klassen komponieren. Es ist auch eine häufige Strategie, um einen doppelten Code zu vermeiden.
Stellen Sie sich vor, wir bauen ein Spiel auf, in dem der Spieler in einer Welt der Tiere lebt. Einige sind Freunde, andere sind feindselig (ein Hund wie ich könnte sagen, dass alle Katzen feindliche Kreaturen sind). Wir könnten eine Klasse -Hostileanimal erstellen, die das Tier erweitert, um als Basisklasse für Katze zu dienen. Irgendwann entscheiden wir uns, Roboter hinzuzufügen, um Menschen Schaden zuzufügen. Das erste, was wir tun, ist die Roboterklasse zu erstellen. Wir haben jetzt zwei Klassen mit ähnlichen Eigenschaften. Sowohl hostileanimal als auch Roboter können beispielsweise angreifen ().
Wenn wir die Feindseligkeit in einer separaten Klasse oder einem Objekt irgendwie definieren könnten, können wir das für beide Katze als Roboter wiederverwenden. Wir können das auf verschiedene Arten tun.
Multiple Vererbung ist ein Merkmal, das einige klassische OOP -Sprachen unterstützen. Wie der Name schon sagt, gibt es uns die Fähigkeit, eine Klasse zu erstellen, die von mehreren Basisklassen erbt. Sehen Sie, wie die Katzenklasse im folgenden Python -Code mehrere Basisklassen erweitert:
<span>class Animal(object): </span> <span>def walk(self): </span> <span># ... </span> <span>class Hostile(object): </span> <span>def attack(self, target): </span> <span># ... </span> <span>class Dog(Animal): </span> <span># ... </span> <span>class Cat(Animal, Hostile): </span> <span># ... </span> dave <span>= Cat(); </span>dave<span>.walk(); </span>dave<span>.attack(target); </span>
an Schnittstelle ist eine gemeinsame Funktion in (typisierten) klassischen OOP -Sprachen. Es ermöglicht uns zu definieren, welche Methoden (und manchmal auch Eigenschaften) eine Klasse enthalten sollte. Wenn diese Klasse nicht der Fall ist, erhöht der Compiler einen Fehler. Der folgende TypeScript -Code würde einen Fehler aufwerfen, wenn CAT den Angriff () oder Walk () -Methoden nicht hätte:
<span>class Animal(object): </span> <span>def walk(self): </span> <span># ... </span> <span>class Hostile(object): </span> <span>def attack(self, target): </span> <span># ... </span> <span>class Dog(Animal): </span> <span># ... </span> <span>class Cat(Animal, Hostile): </span> <span># ... </span> dave <span>= Cat(); </span>dave<span>.walk(); </span>dave<span>.attack(target); </span>
Multiple Vererbung leidet unter dem Diamantproblem (bei dem zwei übergeordnete Klassen dieselbe Methode definieren). Einige Sprachen weichen diesem Problem aus, indem sie andere Strategien wie Mixins implementieren. Mixins sind winzige Klassen, die nur Methoden enthalten. Anstatt diese Klassen zu erweitern, sind Mixins in einer anderen Klasse enthalten. In PHP werden beispielsweise Mixins unter Verwendung von Merkmalen implementiert.
<span>interface Hostile { </span> <span>attack(); </span><span>} </span> <span>class Animal { </span> <span>walk(); </span><span>} </span> <span>class Dog extends Animal { </span> <span>// ... </span><span>} </span> <span>class Cat extends Animal implements Hostile { </span> <span>attack() { </span> <span>// ... </span> <span>} </span><span>} </span>
Wenn Sie nicht die Chance hatten, in ES2015-Klassen einzusteigen oder Sie nicht genug über sie zu wissen, lesen Sie Jeff Motts objektorientiertes JavaScript-ein tiefes Tauchgang in ES6-Klassen, bevor Sie fortfahren.
Kurz gesagt:
Innerhalb des Klassenblocks können wir Eigenschaften dieser Klasse definieren. Für diesen Artikel müssen wir nur Konstrukteure und Methoden verstehen:
Die Klassensyntax ist hauptsächlich syntaktischer Zucker über das Prototypmodell von JavaScript. Anstatt eine Klasse zu erstellen, wird ein Funktionskonstruktor erstellt:
<span>class Animal { </span> <span>// ... </span><span>} </span> <span>trait Hostile { </span> <span>// ... </span><span>} </span> <span>class Dog extends Animal { </span> <span>// ... </span><span>} </span> <span>class Cat extends Animal { </span> <span>use Hostile; </span> <span>// ... </span><span>} </span> <span>class Robot { </span> <span>use Hostile; </span> <span>// ... </span><span>} </span>
Das Mitnehmen hier ist, dass JavaScript keine klassenbasierte OOP-Sprache ist. Man könnte sogar argumentieren, dass die Syntax täuscht und den Eindruck erweckt, dass es ist.
Schnittstellen können nachgeahmt werden, indem eine Dummy -Methode erstellt wird, die einen Fehler verursacht. Einmal geerbt, muss die Funktion überschrieben werden, um den Fehler zu vermeiden:
<span>class Foo {} </span><span>console.log(typeof Foo); // "function" </span>
Wie bereits vorgeschlagen, basiert dieser Ansatz auf der Vererbung. Um mehrere Klassen zu erben, benötigen wir entweder mehrere Vererbung oder Mixins.
Ein anderer Ansatz wäre, eine Dienstprogrammfunktion zu schreiben, die eine Klasse validiert, nachdem sie definiert wurde. Ein Beispiel hierfür findet sich ein Moment in der Wartezeit. JavaScript unterstützt die Mehrfachbeeinigung! Von Andrea Giammarchi. Siehe Abschnitt „Ein grundlegendes Objekt.implement -Funktionsprüfung.“
Zeit, um verschiedene Möglichkeiten zu untersuchen, um mehrere Vererbung und Mixins anzuwenden. Alle untersuchten Strategien unten sind auf Github verfügbar.
pre-ES2015 haben wir Prototypen zur Vererbung verwendet. Alle Funktionen haben eine Prototyp -Eigenschaft. Beim Erstellen einer Instanz mit neuem MyFunction () wird der Prototyp in der Instanz in eine Eigenschaft kopiert. Wenn Sie versuchen, auf eine Eigenschaft zuzugreifen, die sich nicht in der Instanz befindet, versucht die JavaScript -Engine, sie im Prototyp -Objekt nachzuschlagen.
Um demonstrieren, schauen Sie sich den folgenden Code an:
<span>class IAnimal { </span> <span>walk() { </span> <span>throw new Error('Not implemented'); </span> <span>} </span><span>} </span> <span>class Dog extends IAnimal { </span> <span>// ... </span><span>} </span> <span>const robbie = new Dog(); </span>robbie<span>.walk(); // Throws an error </span>
Diese Prototypobjekte können zur Laufzeit erstellt und geändert werden. Anfangs habe ich versucht, Klassen für Tier und feindlich zu verwenden:
<span>class Animal(object): </span> <span>def walk(self): </span> <span># ... </span> <span>class Hostile(object): </span> <span>def attack(self, target): </span> <span># ... </span> <span>class Dog(Animal): </span> <span># ... </span> <span>class Cat(Animal, Hostile): </span> <span># ... </span> dave <span>= Cat(); </span>dave<span>.walk(); </span>dave<span>.attack(target); </span>
Die oben genannte funktioniert nicht, da Klassenmethoden nicht aufzählbar sind. Praktisch bedeutet dies, dass Object.Sign (...) keine Methoden aus Klassen kopiert. Dies macht es auch schwierig, eine Funktion zu erstellen, die Methoden von einer Klasse zur anderen kopiert. Wir können jedoch jede Methode manuell kopieren:
<span>interface Hostile { </span> <span>attack(); </span><span>} </span> <span>class Animal { </span> <span>walk(); </span><span>} </span> <span>class Dog extends Animal { </span> <span>// ... </span><span>} </span> <span>class Cat extends Animal implements Hostile { </span> <span>attack() { </span> <span>// ... </span> <span>} </span><span>} </span>
Eine andere Möglichkeit besteht darin, Klassen auszugeben und Objekte als Mixins zu verwenden. Ein positiver Nebeneffekt ist, dass Mixinobjekte nicht verwendet werden können, um Instanzen zu erstellen, was Missbrauch verhindern kann.
<span>class Animal { </span> <span>// ... </span><span>} </span> <span>trait Hostile { </span> <span>// ... </span><span>} </span> <span>class Dog extends Animal { </span> <span>// ... </span><span>} </span> <span>class Cat extends Animal { </span> <span>use Hostile; </span> <span>// ... </span><span>} </span> <span>class Robot { </span> <span>use Hostile; </span> <span>// ... </span><span>} </span>
pros
cons
Mit ES2015 -Klassen können Sie die Instanz überschreiben, indem Sie ein Objekt im Konstruktor zurückgeben:
<span>class Foo {} </span><span>console.log(typeof Foo); // "function" </span>
Wir können diese Funktion nutzen, um ein Objekt aus mehreren Klassen in einer Unterklasse zu komponieren. Beachten Sie, dass Object.Sign (...) immer noch nicht gut mit Mixin -Klassen funktioniert, also habe ich auch hier Objekte verwendet:
<span>class IAnimal { </span> <span>walk() { </span> <span>throw new Error('Not implemented'); </span> <span>} </span><span>} </span> <span>class Dog extends IAnimal { </span> <span>// ... </span><span>} </span> <span>const robbie = new Dog(); </span>robbie<span>.walk(); // Throws an error </span>
Da sich dies auf eine Klasse (mit nicht-erhöhten Methoden) im obigen Kontext bezieht, kopiert Object.Sign (..., dies) die Methoden der Katze nicht. Stattdessen müssen Sie Felder und Methoden auf dieser Ausdrücke einstellen, damit Object.assisign () in der Lage ist, diese wie so anzuwenden:
<span>function <span>MyFunction</span> () { </span> <span>this.myOwnProperty = 1; </span><span>} </span><span>MyFunction.prototype.myProtoProperty = 2; </span> <span>const myInstance = new MyFunction(); </span> <span>// logs "1" </span><span>console.log(myInstance.myOwnProperty); </span><span>// logs "2" </span><span>console.log(myInstance.myProtoProperty); </span> <span>// logs "true", because "myOwnProperty" is a property of "myInstance" </span><span>console.log(myInstance.hasOwnProperty('myOwnProperty')); </span><span>// logs "false", because "myProtoProperty" isn’t a property of "myInstance", but "myInstance.__proto__" </span><span>console.log(myInstance.hasOwnProperty('myProtoProperty')); </span>
Dieser Ansatz ist nicht praktisch. Da Sie ein neues Objekt anstelle einer Instanz zurückgeben, entspricht es im Wesentlichen zu:
<span>class Animal { </span> <span>walk() { </span> <span>// ... </span> <span>} </span><span>} </span> <span>class Dog { </span> <span>// ... </span><span>} </span> <span>Object.assign(Dog.prototype, Animal.prototype); </span>
Ich denke, wir können zustimmen, dass letzteres lesbarer ist.
pros
cons
Dieser Ansatz nutzt die Fähigkeit von JavaScript, eine Klasse zur Laufzeit zu definieren.
Erstens brauchen wir Basisklassen. In unserem Beispiel dienen Tier und Roboter als Basisklassen. Wenn Sie von vorne anfangen möchten, funktioniert auch eine leere Klasse.
<span>Object.assign(Cat.prototype, { </span> <span>attack: Hostile.prototype.attack, </span> <span>walk: Animal.prototype.walk, </span><span>}); </span>
Als nächstes müssen wir eine Fabrikfunktion erstellen, die eine neue Klasse zurückgibt, die die Klassenbasis erweitert, die als Parameter übergeben wird. Dies sind die Mixins:
<span>const Animal = { </span> <span>walk() { </span> <span>// ... </span> <span>}, </span><span>}; </span> <span>const Hostile = { </span> <span>attack(target) { </span> <span>// ... </span> <span>}, </span><span>}; </span> <span>class Cat { </span> <span>// ... </span><span>} </span> <span>Object.assign(Cat.prototype, Animal, Hostile); </span>
Jetzt können wir jede Klasse an die feindliche Funktion übergeben, die eine neue Klasse zurückgibt, in der feindliche und welche Klasse wir an die Funktion übergeben haben:
<span>class Answer { </span> <span>constructor(question) { </span> <span>return { </span> <span>answer: 42, </span> <span>}; </span> <span>} </span><span>} </span> <span>// { answer: 42 } </span><span>new Answer("Life, the universe, and everything"); </span>
Wir könnten mehrere Klassen durchleiten, um mehrere Mixins anzuwenden:
<span>const Animal = { </span> <span>walk() { </span> <span>// ... </span> <span>}, </span><span>}; </span> <span>const Hostile = { </span> <span>attack(target) { </span> <span>// ... </span> <span>}, </span><span>}; </span> <span>class Cat { </span> <span>constructor() { </span> <span>// Cat-specific properties and methods go here </span> <span>// ... </span> <span>return Object.assign( </span> <span>{}, </span> <span>Animal, </span> <span>Hostile, </span> <span>this </span> <span>); </span> <span>} </span><span>} </span>Sie können Objekt auch als Basisklasse verwenden:
<span>class Cat { </span> <span>constructor() { </span> <span>this.purr = () => { </span> <span>// ... </span> <span>}; </span> <span>return Object.assign( </span> <span>{}, </span> <span>Animal, </span> <span>Hostile, </span> <span>this </span> <span>); </span> <span>} </span><span>} </span>
pros
cons
Als ich mich entschied, dieses Thema zu erforschen und einen Artikel darüber zu schreiben, erwartete ich, dass das prototypische Modell von JavaScript hilfreich für die Generierung von Klassen ist. Da die Klassensyntax die Methoden nicht erhöht, wird die Objektmanipulation viel schwieriger, fast unpraktisch.
Die Klassensyntax könnte die Illusion erzeugen, dass JavaScript eine klassenbasierte OOP-Sprache ist, dies jedoch nicht der Fall ist. Bei den meisten Ansätzen müssen Sie den Prototyp eines Objekts ändern, um die Mehrfachvererbung nachzuahmen. Der letzte Ansatz unter Verwendung von Klassenfabrikfunktionen ist eine akzeptable Strategie für die Verwendung von Mixins zum Komponieren von Klassen.
Wenn Sie prototypbasiertes Programmieren restriktiv finden, sollten Sie sich Ihre Denkweise ansehen. Prototypen bieten beispiellose Flexibilität, die Sie nutzen können.
Wenn Sie aus irgendeinem Grund immer noch klassische Programmierung bevorzugen, möchten Sie möglicherweise Sprachen prüfen, die mit JavaScript kompilieren. Typscript ist beispielsweise ein Superet von JavaScript, das (optionale) statische Typisierung und Muster hinzufügt, die Sie aus anderen klassischen OOP -Sprachen erkennen.
Wirst du einen der oben genannten Ansätze in deinen Projekten verwenden? Haben Sie bessere Ansätze gefunden? Lass es mich in den Kommentaren wissen!
Dieser Artikel wurde Peer von Jeff Mott, Scott Molinari, Vildan Softic und Joan Yin bewertet. Vielen Dank an alle Peer -Rezensenten von SitePoint, die SitePoint -Inhalte so gut wie möglich gemacht haben!
In JavaScript können Sie dem Prototyp eines Objekts eine Eigenschaft hinzufügen, indem Sie einfach einer Eigenschaft im Prototyp -Objekt einen Wert zuweisen. Diese Eigenschaft wird dann von allen aus der Konstruktorfunktion erstellten Objekte vererbt.
Das obige ist der detaillierte Inhalt vonMuster für die Objektvererbung in JavaScript ES2015. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!