15.06.2011 - Prototypenbasierte Programmierung
Finde ich sehr interessant und habe auch schon eine Weile danach gesucht. In Javascript gibt es ja noch keine Klassen wie beispielsweise in C++ oder C# sondern ein entsprechendes Konzept mit prototypische Objekte. Ziel ist es eine Klasse von einer anderen Klasse abzuleiten und dabei die Funktionaliät der Basisklasse zu nutzen ohne dabei die Basisklasse ansich zu verändern und ohne dabei mit den in der Basisklasse getroffenen Bedingungen in Konflikt zu treten. Eine Klasse die von einer anderen Klasse erbt übernimmt deren Verhalten, kann dieses aber bei Bedarf überschreiben und/oder erweitern. Es geht um Spezialisierung und Generalisierung und den daraus resultierenden Effekten – Nutzen, Schnittstellen, Kapselung und so weiter.
Eine Basisklasse könnte zum Beispiel Lebewesen lauten und wäre damit ziemlich generell definiert, eine Spezialisierung davon wäre Hund, Katze oder Mensch. Klassen ansich sind letztendlich nichts anderes als Vorlagen für Objektinstanzen, sprich Objekte im Speicher welche dieser Vorlage entsprechen und sich so wie in der Vorlage definiert verhalten. Von einer Klasse können mehrere Objektinstanzen existieren. So kann es beispielsweise von der Klasse Mensch mehrere Objektinstanzen mit Namen Hans, Dieter, Sandra oder Klarissa geben. Die Klasse kann Methoden wie gehen, laufen, schlafen, sprechen definieren und Eigenschaften festlegen, die dann in den Instanzen bestimmte Werte bekommen (zum Beispiel Augenfarbe, Größe, Geschlecht).
Aber gut, in Javascript kann man nun halt nicht in so ausgeprägter Form Klassen beschreiben wie in anderen bekannten Sprachen, man hat aber die prototypische Objekte zur Verfügung. Über die Fallstricke, die es bei einer unbedachten Verwendung gibt, will und kann ich auch nichts Gescheites sagen. Deshalb beziehe ich mich einfach auf den tollen Wiki-Artikel über prototypenbasierte Programmierung. Es geht darum in Javascript mit den Mitteln der Prototypen eine Beziehung zwischen Objekte aufzubauen, die sich dabei nicht gegenseitig verändern und einem Konzept folgen. Wenn ich im folgendem Code die Begriffe Class verwende, so meine ich letztendlich Objekte in Javascript bzw. speziell Funktionsobjekte, weil diese dem Klassenkonzept am ehesten entsprechen.
// The derived class inherit from base class. function InheritClass(derivedClass, baseClass) { // The behavior prototype points to the base class prototype. var Behavior = function(){}; Behavior.prototype = baseClass.prototype; // The derived class becomes the behavior of the base class. // The derived class cannot change the behavior of the base class. derivedClass.prototype = new Behavior(); derivedClass.prototype.constructor = derivedClass; // To call the base class constructor. derivedClass.prototype.Base = function() { baseClass.apply(this, arguments); } } // "Klasse" Lebewesen definieren, so dass // die Eigenschaft "Gedanke" erstellt/gesetzt wird (pro Instanz) function Lebewesen(gedanke) { this.Gedanke = gedanke; } // Den Prototypen um "Sprich" erweitern, so das ALLE Instanzen davon // diese Funktion haben. Lebewesen.prototype.Sprich = function() { return this.Gedanke; } // "Klasse" Hund definieren, so dass der Konstruktor // der "Basisklasse" aufgerufen wird. (Instanzinitialisierung). function Hund(gedanke) { this.Base(gedanke); } // Hund von Lebewesen ableiten. InheritClass(Hund, Lebewesen); // "Klasse" Mensch definieren, so dass der Konstruktor // der "Basisklasse" aufgerufen wird (Instanzinitialisierung). // Weiterhin die Eigenschaft "Name" erstellen/setzen (pro Instanz). function Mensch(gedanke, name) { this.Base(gedanke); this.Name = name; } // Mensch von Lebewesen ableiten. InheritClass(Mensch, Lebewesen); // Das hier ist jetzt interessant. Auch wenn die Implementierung der Funktionalität // über den Prototypen-Verweis geschieht, so wird die Funktion NUR bei Menschen implementiert! // Genau das ist das tolle an der Funktion InheritClass. // Hund und Lebewesen bleiben unverändert auch wenn Mensch hier schon auf // Lebewesen indirekt verweist. Mensch.prototype.HebeArm = function(name) { if (this.Name == name) { return "Hier"; } return "Wer ist das?"; } // Instanzen erstellen var bakterium = new Lebewesen("Ich teile also bin ich."); var schnuffi = new Hund("wuff"); var tina = new Mensch("Öhm was geht?", "Tina"); var heinz = new Mensch("Ehm ja genau!", "Heinz"); // 1. Durchlauf der Reihe nach alert(bakterium.Sprich()); // "Ich teile also bin ich." alert(schnuffi.Sprich()); // "wuff" alert(tina.Sprich()); // "Öhm was geht?" alert(heinz.Sprich()); // "Ehm ja genau!" // 2. Durchlauf in umgekehrter Reihenfolge alert(heinz.HebeArm("Susanne")); // "Wer ist das?" alert(heinz.HebeArm("Heinz")); // "Hier" alert(tina.HebeArm("Tina")); // "Hier" // Folgende Aufrufe lösen eine Ausnahme aus, weil weder im entspr. Prototypen noch in der Instanz // die Funktion HebeArm eingebaut wurde UND weil auch nicht der Prototyp von Lebewesen verändert wurde. try { alert(schnuffi.HebeArm()); // Ausnahme, kein "Wer ist das?" } catch (err) { alert("Bell Bell Wuff Wuff Heul?"); } try { alert(bakterium.HebeArm()); // Ausnahme, kein "Wer ist das?" } catch (err) { alert("Blub"); }
Sonstiges
In ECMAScript 5 hat man ja viele neue Sachen eingeführt, die dem Programmierer das Leben in mancher Hinsicht erleichtern und den hier vorgestellten Ansatz überflüssig macht, sofern der Browser schon ES5 unterstützt. Anbei einige Links, nicht nur zu ES5, die sich mit dem Thema befassen.
- Details of the object model Class-based vs. prototype-based languages
- ECMAScript 5 Part 1: Reusable Code IE9 Blog
- ECMAScript 5 IE10 Platform Preview
- ECMAScript Language Specification 5.1 Als PDF-Datei ca. 3 MB