JavaScript Prototypen Vererbung
JavaScript Prototypen und Vererbung
JavaScript verwendet prototypbasierte Vererbung statt klassenbasierter. Verstehen Sie die Prototype Chain und moderne Klassen-Syntax.
Prototype Chain Basics
// Jedes Objekt hat einen internen [[Prototype]]
const obj = { name: 'Test' };
// Zugriff via __proto__ (veraltet) oder Object.getPrototypeOf()
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
// Die Prototype Chain
// obj → Object.prototype → null
const arr = [1, 2, 3];
// arr → Array.prototype → Object.prototype → null
function greet() {}
// greet → Function.prototype → Object.prototype → null
Visualisierung
┌─────────────────────────────────────────────────────────────┐
│ PROTOTYPE CHAIN │
├─────────────────────────────────────────────────────────────┤
│ │
│ const person = { name: 'Max' }; │
│ │
│ ┌──────────────┐ │
│ │ person │ │
│ │ name: 'Max' │ │
│ └──────┬───────┘ │
│ │ [[Prototype]] │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ Object.prototype │ │
│ │ toString() │ │
│ │ hasOwnProperty() │ │
│ │ valueOf() │ │
│ └──────────┬───────────────┘ │
│ │ [[Prototype]] │
│ ▼ │
│ null │
│ │
└─────────────────────────────────────────────────────────────┘
Konstruktor-Funktionen (alt)
// Vor ES6: Konstruktor-Funktionen
function Person(name, age) {
this.name = name;
this.age = age;
}
// Methoden auf dem Prototype (geteilt von allen Instanzen)
Person.prototype.greet = function() {
return `Hallo, ich bin ${this.name}`;
};
Person.prototype.birthday = function() {
this.age++;
};
// Instanz erstellen
const max = new Person('Max', 30);
const anna = new Person('Anna', 25);
max.greet(); // "Hallo, ich bin Max"
anna.greet(); // "Hallo, ich bin Anna"
// Beide teilen dieselbe greet-Funktion!
console.log(max.greet === anna.greet); // true
ES6 Klassen (modern)
// ES6 Klassen sind "syntactic sugar" über Prototypen
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// Instanz-Methode (auf Prototype)
greet() {
return `Hallo, ich bin ${this.name}`;
}
// Getter
get info() {
return `${this.name}, ${this.age} Jahre`;
}
// Setter
set info(value) {
[this.name, this.age] = value.split(', ');
}
// Statische Methode (auf der Klasse selbst)
static create(name, age) {
return new Person(name, age);
}
}
const max = new Person('Max', 30);
max.greet(); // "Hallo, ich bin Max"
max.info; // "Max, 30 Jahre"
Person.create('Anna', 25); // Statische Methode
Vererbung mit extends
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} macht ein Geräusch`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // MUSS vor this aufgerufen werden!
this.breed = breed;
}
// Methode überschreiben
speak() {
return `${this.name} bellt: Wuff!`;
}
// Neue Methode
fetch() {
return `${this.name} holt den Ball`;
}
}
class Cat extends Animal {
speak() {
// super.speak() aufrufen + erweitern
return `${super.speak()}: Miau!`;
}
}
const dog = new Dog('Bello', 'Labrador');
dog.speak(); // "Bello bellt: Wuff!"
dog.fetch(); // "Bello holt den Ball"
const cat = new Cat('Minka');
cat.speak(); // "Minka macht ein Geräusch: Miau!"
Object.create() für Vererbung
// Objekt mit spezifischem Prototype erstellen
const personProto = {
greet() {
return `Hallo, ich bin ${this.name}`;
},
introduce() {
return `${this.name}, ${this.age} Jahre alt`;
}
};
// Neues Objekt mit personProto als Prototype
const max = Object.create(personProto);
max.name = 'Max';
max.age = 30;
max.greet(); // "Hallo, ich bin Max"
max.introduce(); // "Max, 30 Jahre alt"
// Mit Property Descriptors
const anna = Object.create(personProto, {
name: { value: 'Anna', writable: true },
age: { value: 25, writable: true }
});
Prototype-Manipulation
// Prototype von bestehendem Objekt ändern (NICHT empfohlen!)
const obj = { a: 1 };
const proto = { b: 2 };
Object.setPrototypeOf(obj, proto);
console.log(obj.b); // 2
// Besser: Object.create von Anfang an nutzen
const obj2 = Object.create(proto);
obj2.a = 1;
// Prototype-Kette prüfen
console.log(proto.isPrototypeOf(obj2)); // true
// Eigene Property vs. geerbte Property
console.log(obj2.hasOwnProperty('a')); // true (eigene)
console.log(obj2.hasOwnProperty('b')); // false (geerbt)
console.log('b' in obj2); // true (irgendwo in der Kette)
Private Felder (ES2022)
class BankAccount {
// Private Felder mit #
#balance = 0;
#transactions = [];
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#logTransaction('deposit', amount);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
this.#logTransaction('withdraw', amount);
return amount;
}
throw new Error('Nicht genug Guthaben');
}
// Private Methode
#logTransaction(type, amount) {
this.#transactions.push({ type, amount, date: new Date() });
}
get balance() {
return this.#balance;
}
}
const account = new BankAccount(100);
account.deposit(50);
account.balance; // 150
// account.#balance; // SyntaxError! Privat!
Mixins für mehrfache Vererbung
// JavaScript hat keine echte Mehrfachvererbung
// Lösung: Mixins
const CanFly = {
fly() {
return `${this.name} fliegt`;
}
};
const CanSwim = {
swim() {
return `${this.name} schwimmt`;
}
};
const CanWalk = {
walk() {
return `${this.name} läuft`;
}
};
class Animal {
constructor(name) {
this.name = name;
}
}
// Mixins anwenden
class Duck extends Animal {}
Object.assign(Duck.prototype, CanFly, CanSwim, CanWalk);
const donald = new Duck('Donald');
donald.fly(); // "Donald fliegt"
donald.swim(); // "Donald schwimmt"
donald.walk(); // "Donald läuft"
instanceof und Typprüfung
class Animal {}
class Dog extends Animal {}
const dog = new Dog();
// instanceof prüft die Prototype-Kette
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
// Konstruktor prüfen
console.log(dog.constructor === Dog); // true
// Symbol.hasInstance für eigene instanceof-Logik
class Even {
static [Symbol.hasInstance](obj) {
return typeof obj === 'number' && obj % 2 === 0;
}
}
console.log(4 instanceof Even); // true
console.log(5 instanceof Even); // false
💡 Best Practice:
Nutzen Sie ES6 Klassen für bessere Lesbarkeit. Vermeiden Sie das Ändern von eingebauten Prototypen (z.B. Array.prototype). Für echte Kapselung verwenden Sie private Felder (#) statt Closures.