OOP mit JavaScript – schnell erklärt

Im Moment widme ich mich etwas der Programmierung mit JavaScript. Und ich muss sagen, objektorientierte Programmierung mit JavaScript ist schon etwas …. anders … als z.B. in PHP oder Objective-C. Dieser Artikel ist für Programmierer gedacht welche noch nicht viel mit JavaScript zu tun hatten und wissen wollen wie man „Klassen“ definiert (Eine kleine Warnung: „Klassen“-Definitionen gibt es in JavaScript nicht!).

Nehmen wir uns als Beispiel einmal eine einfach Klasse „Person“ welche einen „Namen“ und mehrere E-Mail-Adressen habe kann. Es existiert auch eine einfache Methode „printName()“ welche den Namen der Person ausgibt, „addEmail()“ welche eine E-Mail-Adresse hinzufügt und „printEmails()“ welche die E-Mail-Adressen ausgibt.

In PHP würde das so aussehen:

class Person()
{
    protected $name = '';
    protected $emails = array();

    public function __construct($name = '')
    {
        $this->name = $name;
    }

    public function addEmail($email)
    {
        $this->emails[] = $email;
    }

    public function printName()
    {
        echo "Der Name lautet: " . $this->name;
    }

    public function printEmails()
    {
        echo "Folgende E-Mail-Adressen sind hinterlegt: " . implode(", ", $this->emails);
    }
}

Wie gesagt ist dies ein einfaches Beispiel welches wir nun in JavaScript umsetzen möchten. Ich möchte kurz ein paar Entwurfsmuster vorstellen über die dies machbar ist – inkl. der Nachteile (und am Ende natürlich der „optimale“ Weg) 🙂

Contructor Pattern

Einer der einfachsten Möglichkeiten eine „Klassendefinition“ (wie gesagt so etwas gibt es in JS nicht!) zu erstellen ist über das „Contructor Pattern“. Dies würde in JavaScript so aussehen:

function Person(name)
{
    this.name = name;
    this.emails = [];  // das gleiche wie = new Array()
    this.printName = function() {
        alert('Der Name lautet:' + this.name);
    }
    this.printEmails = function() {
        alert('Folgende E-Mail-Adressen sind hinterlegt: ' + this.emails.join(', '));
    }
    this.addEmail = function(email) {
        this.emails.push(email);
    }
}

Hier ein paar Beispiele:

var sven = new Person("Sven");

// Ausgabe des Namens
sven.printName();

// Fügt eine E-Mail-Adresse hinzu
sven.addEmail("sven@fasty.de");

// Gibt die E-Mail Adressen aus
sven.printEmails();

Alles sollte so funktionieren wie erwartet! Doch warum sollte man diese Art der Objekt-Erzeugung nicht nutzen? Der Hauptgrund ist dass bei jeder Objekt-Erzeugung (new Person()) alle Methoden noch einmal neu erstellt werden! Dies kann bei großen Objekten doch stark auf den Speicher gehen und ist auch nicht Sinn der Sache, also probieren wir mal ein anderes Pattern:

Prototype Pattern

Aaaah, irgendwie habe ich den Begriff „Prototype“ in Bezug auf JavaScript und OOP schon einmal gehört. In JavaScript sollen „Prototypen“ doch so etwas sein was wir in anderen Sprachen „Klassen“ nennen.

Hier ist eine Möglichkeit wie unser Beispiel mit Prototypen umzusetzen ist. Ein Nachteil vorweg: im Konstruktor kann der Name nicht mit übergeben werden:

function Person() {
}

Person.prototype.name = '';
Person.prototype.emails = [];
Person.prototype.printName = function() {
    alert('Der Name lautet:' + this.name);
}
Person.prototype.printEmails = function() {
    alert('Folgende E-Mail-Adressen sind hinterlegt: ' + this.emails.join(', '));
}
Person.prototype.addEmail = function(email) {
    this.emails.push(email);
}

Eine beliebtere Schreibweise ist mit sog. „Object Literals“ zu arbeiten die man von JSON her kennt:

function Person() {
}

Person.prototype = {
    constructor : Person,
    name : '',
    emails : [],
    printName : function() {
        alert('Der Name lautet:' + this.name);
    },
    printEmails : function() {
        alert('Folgende E-Mail-Adressen sind hinterlegt: ' + this.emails.join(', '));
    },
    addEmail : function(email) {
        this.emails.push(email);
    }
}

Bei dieser Schreibweise ist nur darauf zu achten dass man den Konstruktor über die Eigenschaft „constructor“ korrekt verknüpft da ansonsten der selbst definierte Konstruktor mit einem Standard überschrieben wird (dies passiert nur in dieser Schreibweise).

Das sieht doch schon fast wie eine richtige „Klassendefinition“ aus, oder? Sehen wir uns mal einige Beispiele damit an:

// Wir erstellen 2 Objekte "sven" und "katrin"
var sven = new Person();
var katrin = new Person();

// Wir setzen die Namen der Objekte (wir greifen mal direkt auf die Eigenschaften zu)
sven.name = 'Sven';
katrin.name = 'Katrin';

sven.printName();
// Dies gibt "Sven" aus - wie erwartet

katrin.printName();
// Dies gibt "Katrin" aus - wie erwartet

// Fügen wir einige E-Mail-Adressen für Sven hinzu und geben diese aus:
sven.addEmail('sven@fasty.de');
sven.printEmails();
// Dies gibt wie erwartet 'sven@fasty.de' aus

// Nun fügen wir für Katrin eine E-Mail-Adresse hinzu und geben diese aus:
katrin.addEmail('katrin@fasty.de');
katrin.printEmails();
// FEHLER - es wird ausgegeben 'sven@fasty.de, katrin@fasty.de'

Aber wieso der Fehler? Der Grund liegt in der Eigenschaft „emails“, welcher ein „Refernz-Typ“, quasi ein Objekt ist. Diese Referenztypen zeigen bei jeder Objekt-Erstellung auf die gleiche Adresse da sie einmal im Prototyp erstellt wurden und nicht bei jeder Objekt-Erstellung neu erzeugt werden.

Wie bringe ich JavaScript also dazu bei jeder Objekt-Erzeugung das Array für die E-Mails neu zu erstellen?

Kombination aus „Prototype“ und „Constructor“ Pattern

Die Antwort liegt in einer Kombination aus beiden oben genannten Entwurfsmustern (deshalb habe ich auch etwas „ausgeholt“.

Wir verwenden das „Contructor Pattern“ um die Eigenschaften zu erzeuge. Beim „Constructor Pattern“ werden diese ja für jedes Objekt neu generiert (was zwar unschön für die Funktionen war), und für die Methonden-Definition wird das „Prototype Pattern“ verwendet.

Zusammengefasst sieht das endgültige Beispiel also so aus:

// Eigenschaften werden im Kontruktor definiert
function Person(name) {
    this.name = name;
    this.emails = [];
}

// Methoden werden im Prototpe definert. Wichtig ist auch die Referenz auf den Konstruktor
Person.Prototype = {
    constructor : Person,
    printName : function() {
        alert('Der Name lautet:' + this.name);
    },
    printEmails : function() {
        alert('Folgende E-Mail-Adressen sind hinterlegt: ' + this.emails.join(', '));
    },
    addEmail : function(email) {
        this.emails.push(email);
    }
}

Das war’s, so funktionieren auch Eigenschaften welche „Referenz-Typen“ sind (wie z.B. Arrays) korrekt. Man kann nun verschiedene Objekte von „Personen“ erzeugen, jedem seine eigene E-Mail-Adressen zuweisen, diese werden nun für die korrekten Objekte gespeichert und sind nicht mehr in allen Objekten gleich.

Über Fasty

iOS- und PHP-Entwickler My Google Profile+
Dieser Beitrag wurde unter JavaScript abgelegt und mit verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.