JavaScript implementiert objektorientierte Programmierung auf eine einzigartige Weise: statt klassenbasierter Vererbung, wie sie in Sprachen wie Java oder C++ üblich ist, nutzt JavaScript ein prototypenbasiertes Vererbungsmodell. Dieses Konzept ist fundamental für das tiefere Verständnis der Sprache, selbst wenn moderne Syntax wie ES6-Klassen verwendet wird.
In JavaScript ist fast alles ein Objekt. Jedes Objekt hat einen Verweis auf einen Prototypen - ein anderes Objekt, von dem es Eigenschaften und Methoden erbt. Diese Prototypen-Kette bildet das Rückgrat der Objektvererbung in JavaScript.
// Ein einfaches Objekt
const fahrzeug = {
antrieb: 'Motor',
fahren: function() {
return `Fahrzeug fährt mit ${this.antrieb}`;
}
};
// Ein weiteres Objekt, das fahrzeug als Prototyp verwendet
const auto = Object.create(fahrzeug);
auto.räder = 4;
auto.antrieb = 'Benzinmotor';
console.log(auto.fahren()); // "Fahrzeug fährt mit Benzinmotor"
console.log(auto.räder); // 4In diesem Beispiel: - fahrzeug ist ein Objekt mit
eigenen Eigenschaften - auto ist ein neues Objekt, das
fahrzeug als Prototyp hat - auto erbt die
fahren()-Methode von seinem Prototyp - auto
überschreibt die Eigenschaft antrieb mit einem eigenen
Wert
Wenn auf eine Eigenschaft eines Objekts zugegriffen wird, sucht
JavaScript zunächst im Objekt selbst. Wird die Eigenschaft dort nicht
gefunden, erfolgt die Suche im Prototyp des Objekts, dann im Prototyp
des Prototyps und so weiter, bis entweder die Eigenschaft gefunden oder
das Ende der Kette (meist Object.prototype) erreicht
wird.
// Die Prototypen-Kette in Aktion
console.log(auto.toString()); // Erbt von Object.prototype
console.log(auto.hasOwnProperty('räder')); // true - prüft eigene Eigenschaften
console.log(auto.hasOwnProperty('fahren')); // false - keine eigene EigenschaftVor ES6 war der häufigste Weg, um Objekte mit gemeinsamen
Eigenschaften zu erstellen, die Verwendung von Constructor-Funktionen in
Kombination mit dem new-Operator:
function Fahrzeug(antrieb) {
this.antrieb = antrieb;
}
// Methoden zum Prototyp hinzufügen
Fahrzeug.prototype.fahren = function() {
return `Fahrzeug fährt mit ${this.antrieb}`;
};
// Objekte mit dem Constructor erstellen
const auto = new Fahrzeug('Benzinmotor');
const motorrad = new Fahrzeug('Zweitaktmotor');
console.log(auto.fahren()); // "Fahrzeug fährt mit Benzinmotor"
console.log(motorrad.fahren()); // "Fahrzeug fährt mit Zweitaktmotor"Was passiert beim Aufruf mit new: 1. Ein neues, leeres
Objekt wird erstellt 2. Der Prototyp dieses Objekts wird auf
Fahrzeug.prototype gesetzt 3. Die Funktion
Fahrzeug wird mit this als das neue Objekt
ausgeführt 4. Das neue Objekt wird zurückgegeben (sofern die Funktion
nicht explizit etwas anderes zurückgibt)
Die prototypische Vererbung ermöglicht es, Objekte zu erstellen, die andere Objekte erweitern:
// Basis-Constructor
function Fahrzeug(antrieb) {
this.antrieb = antrieb;
}
Fahrzeug.prototype.starten = function() {
return `${this.antrieb} startet`;
};
// Abgeleiteter Constructor
function Auto(antrieb, marke) {
// "Super"-Aufruf
Fahrzeug.call(this, antrieb);
this.marke = marke;
this.räder = 4;
}
// Vererbung einrichten
Auto.prototype = Object.create(Fahrzeug.prototype);
Auto.prototype.constructor = Auto; // Constructor-Referenz korrigieren
// Eigene Methoden für Auto
Auto.prototype.hupen = function() {
return `${this.marke} hupt`;
};
const meinAuto = new Auto('Dieselmotor', 'VW');
console.log(meinAuto.starten()); // "Dieselmotor startet"
console.log(meinAuto.hupen()); // "VW hupt"Es ist wichtig, zwei verwandte, aber unterschiedliche Konzepte zu verstehen:
Das prototype-Property einer Funktion: Wenn eine
Funktion als Constructor verwendet wird, bestimmt ihr
prototype-Property, welches Objekt als Prototyp für die mit
new erstellten Instanzen dient.
Der interne [[Prototype]]-Verweis eines Objekts:
Dies ist der tatsächliche Prototyp eines Objekts, auf den über
Object.getPrototypeOf() oder das nicht standardisierte
__proto__ zugegriffen werden kann.
function Test() {}
const obj = new Test();
// Beziehungen zeigen
console.log(Object.getPrototypeOf(obj) === Test.prototype); // true
console.log(obj.__proto__ === Test.prototype); // true, aber __proto__ ist veraltetJavaScript bietet präzise Kontrolle über Objekteigenschaften durch Deskriptoren:
const person = {};
Object.defineProperty(person, 'name', {
value: 'Max',
writable: true, // Kann überschrieben werden
enumerable: true, // Erscheint in for...in Schleifen
configurable: true // Kann gelöscht oder rekonfiguriert werden
});
// Getter/Setter definieren
let _alter = 30;
Object.defineProperty(person, 'alter', {
get: function() { return _alter; },
set: function(wert) {
if (wert > 0) _alter = wert;
},
enumerable: true,
configurable: true
});
person.alter = 35;
console.log(person.alter); // 35ES6+ bietet verbesserte Möglichkeiten zur Arbeit mit Prototypen:
// Object.setPrototypeOf() - nachträgliche Änderung des Prototyps
const tier = {
atmen: function() { return 'Tier atmet'; }
};
const hund = {
bellen: function() { return 'Wau!'; }
};
Object.setPrototypeOf(hund, tier);
console.log(hund.atmen()); // "Tier atmet"
// Object.getPrototypeOf() - sicherer Zugriff auf den Prototyp
console.log(Object.getPrototypeOf(hund) === tier); // true