Grundlegendes zu Prototypen und Vererbung in JavaScript

Einführung

JavaScript ist eine * prototypbasierte Sprache *, dh Objekteigenschaften und Methoden können über verallgemeinerte Objekte, die geklont und erweitert werden können, gemeinsam genutzt werden. Dies ist als prototypische Vererbung bekannt und unterscheidet sich von der Klassenvererbung. Unter den gängigen objektorientierten Programmiersprachen ist JavaScript relativ einzigartig, da andere bekannte Sprachen wie PHP, Python und Java klassenbasierte Sprachen sind, die Klassen stattdessen als Blaupausen für Objekte definieren.

In diesem Lernprogramm erfahren Sie, was Objektprototypen sind und wie Sie die Konstruktorfunktion verwenden, um Prototypen in neue Objekte zu erweitern. Wir werden auch etwas über Vererbung und die Prototypenkette lernen.

JavaScript-Prototypen

In Understanding Objects in JavaScript haben wir den Objektdatentyp, das Erstellen eines Objekts und das Zugreifen auf und Ändern von Objekten erläutert Eigenschaften. Jetzt werden wir lernen, wie Prototypen verwendet werden können, um Objekte zu erweitern.

Jedes Objekt in JavaScript hat eine interne Eigenschaft mit dem Namen "+ +". Wir können dies demonstrieren, indem wir ein neues, leeres Objekt erstellen.

let x = {};

Dies ist die Art und Weise, wie wir normalerweise ein Objekt erstellen würden. Beachten Sie jedoch, dass dies auch mit dem Objektkonstruktor erreicht werden kann: + let x = new Object () +.

Um den + + dieses neu erstellten Objekts zu finden, verwenden wir die Methode + getPrototypeOf () +.

Object.getPrototypeOf(x);

Die Ausgabe besteht aus mehreren integrierten Eigenschaften und Methoden.

Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Eine andere Möglichkeit, den Prototyp zu finden, ist die Eigenschaft + proto . https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto [` proto `] ist eine Eigenschaft, die den internen ` +` verfügbar macht eines Objekts.

x.__proto__;

Die Ausgabe ist dieselbe, als ob Sie "+ getPrototypeOf () +" verwendet hätten.

Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Es ist wichtig, dass jedes Objekt in JavaScript einen + + hat, da hierdurch zwei oder mehr Objekte verknüpft werden können.

Objekte, die Sie erstellen, haben ein "+ ", ebenso wie eingebaute Objekte, wie " Date" und "+ Array I". Über die Eigenschaft + prototype + kann von einem Objekt zum anderen auf diese interne Eigenschaft verwiesen werden, wie wir später in diesem Lernprogramm sehen werden.

Vererbung von Prototypen

Wenn Sie versuchen, auf eine Eigenschaft oder Methode eines Objekts zuzugreifen, durchsucht JavaScript zunächst das Objekt selbst. Wird es nicht gefunden, durchsucht es das Objekt nach "+ ". Wenn nach der Abfrage des Objekts und seines ` +` immer noch keine Übereinstimmung gefunden wird, überprüft JavaScript den Prototyp des verknüpften Objekts und setzt die Suche fort, bis das Ende der Prototypkette erreicht ist.

Am Ende der Prototypenkette befindet sich https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype [+ Object.prototype +]. Alle Objekte erben die Eigenschaften und Methoden von https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object [+ Object +]. Jeder Versuch, über das Ende der Kette hinaus zu suchen, führt zu + null +.

In unserem Beispiel ist "+ x " ein leeres Objekt, das von " Object " erbt. ` x ` kann jede Eigenschaft oder Methode verwenden, die ` Object ` hat, z. B. ` toString () +`.

x.toString();
Output[object Object]

Diese Prototypkette ist nur ein Glied lang. + x ++ Objekt +. Wir wissen das, denn wenn wir versuchen, zwei + + -Eigenschaften miteinander zu verketten, ist dies + null +.

x.__proto__.__proto__;
Outputnull

Schauen wir uns einen anderen Objekttyp an. Wenn Sie Erfahrung mit Working with Arrays in JavaScript haben, wissen Sie, dass sie über viele integrierte Methoden verfügen, z. B. + pop () + `und + () + drücken. Der Grund, warum Sie beim Erstellen eines neuen Arrays Zugriff auf diese Methoden haben, ist, dass jedes von Ihnen erstellte Array Zugriff auf die Eigenschaften und Methoden des `+ Array.prototype + hat.

Wir können dies testen, indem wir ein neues Array erstellen.

let y = [];

Denken Sie daran, dass wir es auch als Array-Konstruktor schreiben können, + let y = new Array () +.

Wenn wir uns das "+ " des neuen " y " - Arrays ansehen, werden wir sehen, dass es mehr Eigenschaften und Methoden hat als das " x " - Objekt. Es hat alles von ` Array.prototype +` geerbt.

y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]

Sie werden eine "+ constructor " -Eigenschaft auf dem Prototyp bemerken, die auf " Array () " gesetzt ist. Die Eigenschaft ` constructor +` gibt die Konstruktorfunktion eines Objekts zurück. Hierbei handelt es sich um einen Mechanismus, mit dem Objekte aus Funktionen erstellt werden.

Wir können jetzt zwei Prototypen miteinander verketten, da unsere Prototypenkette in diesem Fall länger ist. Es sieht aus wie + y ++ Array →` + Object N`.

y.__proto__.__proto__;
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Diese Kette bezieht sich nun auf "+ Object.prototype ". Wir können den internen ` ` gegen die Eigenschaft ` prototype +` der Konstruktorfunktion testen, um festzustellen, dass sie auf dasselbe verweisen.

y.__proto__ === Array.prototype;            // true
y.__proto__.__proto__ === Object.prototype; // true

Wir können auch die Methode + isPrototypeOf () + verwenden, um dies zu erreichen.

Array.prototype.isPrototypeOf(y);      // true
Object.prototype.isPrototypeOf(Array); // true

Mit dem Operator "+ instanceof " können Sie testen, ob die Eigenschaft " prototype +" eines Konstruktors an einer beliebigen Stelle in der Prototypenkette eines Objekts angezeigt wird.

y instanceof Array; // true

Zusammenfassend haben alle JavaScript-Objekte eine versteckte, interne Eigenschaft "+ " (die in einigen Browsern durch " proto " angezeigt werden kann). Objekte können erweitert werden und übernehmen die Eigenschaften und Methoden von ` +` ihres Konstruktors.

Diese Prototypen können verkettet werden, und jedes zusätzliche Objekt erbt alles in der Kette. Die Kette endet mit dem + Object.prototype +.

Konstruktorfunktionen

  • Konstruktorfunktionen * sind Funktionen, mit denen neue Objekte erstellt werden. Der https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new [+ new + Operator] wird verwendet, um neue Instanzen basierend auf einer Konstruktorfunktion zu erstellen. Wir haben einige integrierte JavaScript-Konstruktoren gesehen, wie z. B. "+ new Array () " und " new Date () +", aber wir können auch eigene benutzerdefinierte Vorlagen erstellen, aus denen neue Objekte erstellt werden.

Angenommen, wir erstellen ein sehr einfaches, textbasiertes Rollenspiel. Ein Benutzer kann einen Charakter auswählen und dann auswählen, über welche Charakterklasse er verfügen soll, z. B. Krieger, Heiler, Dieb und so weiter.

Da jedes Zeichen viele Merkmale aufweist, z. B. einen Namen, eine Stufe und Trefferpunkte, ist es sinnvoll, einen Konstruktor als Vorlage zu erstellen. Da jedoch jede Charakterklasse sehr unterschiedliche Fähigkeiten haben kann, möchten wir sicherstellen, dass jeder Charakter nur Zugriff auf seine eigenen Fähigkeiten hat. Schauen wir uns an, wie wir dies mit der Vererbung von Prototypen und Konstruktoren erreichen können.

Zu Beginn ist eine Konstruktorfunktion nur eine reguläre Funktion. Es wird zu einem Konstruktor, wenn es von einer Instanz mit dem Schlüsselwort + new + aufgerufen wird. In JavaScript wird der erste Buchstabe einer Konstruktorfunktion nach Konvention großgeschrieben.

characterSelect.js

// Initialize a constructor function for a new Hero
function Hero(name, level) {
 this.name = name;
 this.level = level;
}

Wir haben eine Konstruktorfunktion namens "+ Hero " mit zwei Parametern erstellt: " name " und " level ". Da jedes Zeichen einen Namen und eine Stufe hat, ist es sinnvoll, dass jedes neue Zeichen diese Eigenschaften hat. Das Schlüsselwort " this " verweist auf die neu erstellte Instanz. Wenn Sie also " this.name " für den Parameter " name " festlegen, wird für das neue Objekt die Eigenschaft " name +" festgelegt.

Jetzt können wir mit + new + eine neue Instanz erstellen.

let hero1 = new Hero('Bjorn', 1);

Wenn wir "+ hero1 +" auslagern, sehen wir, dass ein neues Objekt mit den neuen Eigenschaften wie erwartet erstellt wurde.

OutputHero {name: "Bjorn", level: 1}

Wenn wir nun den + + von + hero1 + bekommen, können wir den + Konstruktor + als + Hero () + sehen. (Denken Sie daran, dies hat die gleiche Eingabe wie "+ hero1 . proto +", ist jedoch die richtige Methode.)

Object.getPrototypeOf(hero1);
Outputconstructor: ƒ Hero(name, level)

Möglicherweise stellen Sie fest, dass wir im Konstruktor nur Eigenschaften und keine Methoden definiert haben. In JavaScript ist es üblich, Methoden für den Prototyp zu definieren, um die Effizienz und die Lesbarkeit des Codes zu verbessern.

Wir können + Hero + mit + prototype + eine Methode hinzufügen. Wir erstellen eine "+ greet () +" - Methode.

characterSelect.js

...
// Add greet method to the Hero prototype
Hero.prototype.greet = function () {
 return `${this.name} says hello.`;
}

Da sich "+ greet () " im " Prototyp " von " Hero " befindet und " hero1 " eine Instanz von " Hero " ist, steht die Methode " hero1 +" zur Verfügung.

hero1.greet();
Output"Bjorn says hello."

Wenn Sie den "+ " von Hero überprüfen, wird " greet () +" jetzt als verfügbare Option angezeigt.

Das ist gut, aber jetzt wollen wir Charakterklassen für die Helden erstellen. Es wäre nicht sinnvoll, alle Fähigkeiten für jede Klasse in den Konstruktor "+ Hero " zu schreiben, da verschiedene Klassen unterschiedliche Fähigkeiten haben. Wir wollen neue Konstruktorfunktionen erstellen, aber wir wollen auch, dass sie mit dem ursprünglichen ` Hero +` verbunden werden.

Wir können die Methode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call [+ call () +] verwenden, um Eigenschaften von einem Konstruktor in einen anderen zu kopieren Konstrukteur. Erstellen wir einen Krieger und einen Heiler.

characterSelect.js

...
// Initialize Warrior constructor
function Warrior(name, level, weapon) {
 // Chain constructor with call
 Hero.call(this, name, level);

 // Add a new property
 this.weapon = weapon;
}

// Initialize Healer constructor
function Healer(name, level, spell) {
 Hero.call(this, name, level);

 this.spell = spell;
}

Beide neuen Konstruktoren haben jetzt die Eigenschaften von "+ Hero" und einige einzigartige. Wir werden die Methode "+ attack () " zu " Warrior " und die Methode " heal () " zu " Healer +" hinzufügen.

characterSelect.js

...
Warrior.prototype.attack = function () {
 return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
 return `${this.name} casts ${this.spell}.`;
}

Zu diesem Zeitpunkt erstellen wir unsere Charaktere mit den zwei neuen verfügbaren Charakterklassen.

characterSelect.js

const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

+ hero1 + wird nun mit den neuen Eigenschaften als + Warrior + erkannt.

OutputWarrior {name: "Bjorn", level: 1, weapon: "axe"}

Wir können die neuen Methoden verwenden, die wir für den Prototyp "+ Warrior +" festgelegt haben.

hero1.attack();
Console"Bjorn attacks with the axe."

Aber was passiert, wenn wir versuchen, Methoden weiter unten in der Prototypenkette einzusetzen?

hero1.greet();
OutputUncaught TypeError: hero1.greet is not a function

Prototyp-Eigenschaften und -Methoden werden nicht automatisch verknüpft, wenn Sie "+ call () " verwenden, um Konstruktoren zu verketten. Wir werden ` Object.create () +` verwenden, um die Prototypen zu verknüpfen. Achten Sie dabei darauf, diese zu platzieren, bevor zusätzliche Methoden erstellt und zum Prototyp hinzugefügt werden.

characterSelect.js

...
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);

// All other prototype methods added below
...

Jetzt können wir erfolgreich Prototypmethoden von "+ Hero " auf eine Instanz eines " Warrior" oder "+ Healer" anwenden.

hero1.greet();
Output"Bjorn says hello."

Hier ist der vollständige Code für unsere Charaktererstellungsseite.

characterSelect.js

// Initialize constructor functions
function Hero(name, level) {
 this.name = name;
 this.level = level;
}

function Warrior(name, level, weapon) {
 Hero.call(this, name, level);

 this.weapon = weapon;
}

function Healer(name, level, spell) {
 Hero.call(this, name, level);

 this.spell = spell;
}

// Link prototypes and add prototype methods
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);

Hero.prototype.greet = function () {
 return `${this.name} says hello.`;
}

Warrior.prototype.attack = function () {
 return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
 return `${this.name} casts ${this.spell}.`;
}

// Initialize individual character instances
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

Mit diesem Code haben wir unsere Klasse "+ Hero " mit den Basiseigenschaften erstellt, zwei Zeichenklassen mit den Namen " Warrior " und " Healer +" aus dem ursprünglichen Konstruktor erstellt, den Prototypen Methoden hinzugefügt und einzelne Zeicheninstanzen erstellt.

Fazit

JavaScript ist eine prototypbasierte Sprache und funktioniert anders als das traditionelle klassenbasierte Paradigma, das viele andere objektorientierte Sprachen verwenden.

In diesem Tutorial haben wir gelernt, wie Prototypen in JavaScript funktionieren und wie Objekteigenschaften und -methoden über die verborgene Eigenschaft + + verknüpft werden, die alle Objekte gemeinsam haben. Wir haben auch gelernt, wie benutzerdefinierte Konstruktorfunktionen erstellt werden und wie die Prototypvererbung funktioniert, um Eigenschafts- und Methodenwerte weiterzugeben.