/ code

JavaScript OOP

Entgegen oft vorgebrachter Meinung, eignet sich JavaScript durchaus zur Programmierung im klassischen OOP Schema. Da es sich allerdings um eine prototypisierende Sprache handelt, ist das Vorgehen hierfür ein anderes, als aus Java oder C++ gewohnt.

In diesem Artikel werde ich eine Sammlung der am häufigsten gebrauchten Muster zeigen, um dem Um- und Einsteiger über die Untiefen von OOP in JavaScript zu helfen.

Klassen und Objekte

In JavaScript ist so ziemlich alles ein Objekt:

typeof new Array // -> "object"
typeof new String // -> "object"
typeof {} // -> "object"
typeof "foo" // -> "string"

Objekte lassen sich dabei einfach als Codeblock erzeugen:

var obj = { one: 1, two: 2 };

Sie lassen sich jedoch auch aus Funktionen erzeugen:

function Klass () {
    
    var self = this;
    
    this.one = 1;
    this.two = 2;
};

JSON.stringify(new Klass) // -> {"one":1,"two":2}

Die Funktion wird hierbei zum Konstruktor der Klasse. Methoden und Eigenschaften der Klasse können entwerder im Konstruktor oder im Prototyp der Klasse definiert werden (hierzu später mehr):

Klass.prototype.three = 3;

JSON.stringify(new Klass) // -> {"one":1,"two":2,"three":3}

Vererbung

Wir definieren uns eine Klasse von der wir erben möchten:

function Fahrzeug (name) {
    this.name = name;
};

Fahrzeug.prototype.get_name = function () {
    return this.name;
};

Fahrzeug.prototype.geräusch = function () {
    return this.klang || 'wroom';
};

Nun definieren wir uns eine zweite Klasse, die die Eigenschaften der ersten übernimmt:

function Elektroauto (name) {
    this.name = name;
    this.klang = 'surr';
};

Elektroauto.prototype = new Fahrzeug();

Elektroauto.prototype.geräusch = function () {
    return null;
};

var tesla = new Elektroauto('Tesla');
var geräusch = tesla.geräusch(); // -> null
var name = tesla.get_name(); // -> 'Tesla'

Um den etwas unübersichtlichen Syntax der Vererbunng zu vereinfachen, lässt sich mittels der method Methode eine Funktion inherits definieren:

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

Function.method('inherits', function (Parent) {
    this.prototype = new Parent();
    return this;
});

Somit kann die Vererbung in einem Ausdruck geschrieben werden:

var Elektroauto = function (name) {
    this.name = name;
    this.klang = 'surr';
}
    .inherits(Fahrzeug)
    .method('geräusch', function () {
        return null;
    });

Private Property Hiding

JavaScript Objekte können von sich aus keine privaten Variablen oder Methoden besitzen. Dennoch ist es möglich private Variablen und Methoden in einer Klasse zu definieren, die dann auch nach Außen hin versteckt sind.

Zuerst jedoch die Definition von public members. Dies kann einmal im Konstruktor einer Klasse geschehen:

function Klass (param) {
    this.foo = param;
};

var instance = new Klass('abc');
instance.foo; // -> 'abc'

oder im Prototyp einer Klasse:

Klass.prototype.bar = function (inp) {
    return this.foo + inp;
};

instance.bar('def'); // -> 'abcdef'

Public members können einfach im Konstruktor einer Klasse erzeugt werden, indem sie an this angehängt werden.

Alle im Konstruktor mittels var definierten Variablen und Funktionen sind nur im Scope des Konstruktors sichtbar und somit vor Zugriffen von Außen geschützt:

function Klass (param) {
    this.foo = param;
    var self = this;
    var private = 1;
};

var instance = new Klass('abc');
instance // -> Klass {foo: 'abc'}

Hierbei sollte beachtet werden, dass solche Attribute auch von den eigenen public members der Klasse nicht zugänglich sind, sondern ausschließlich den private members.

function Klass (param) {
    
    function baz () {
        return ( private > 0 );
    };
    
    this.foo = param;
    var self = this;
    var private = 1;
};

Eine Variablen self o.ä. als var self = this; zu definiert, erleichtert es später die aktuelle Instanz der Klasse ansprechen zu können. Innerhalb privater Methoden wird this später auf die private Methode selbst zeigen und die Klasse somit nicht mehr zugänglich sein.

Wie kann nun eine Methode definiert werden, die sowohl nach außen sichtbar ist, als auch auf private members zugreifen kann?
Die Lösung stellen von Douglas Crockford "privileged" genannte members dar:

function Klass (param) {
    
    function baz () {
        return ( private > 0 );
    };
    
    this.foo = param;
    var self = this;
    var private = 1;
    
    this.privileged = function () {
        return baz() ? self.foo : void(0);
    };
};

Hinweis: Die Syntax function name () {} und var name = function () {} erzeugen völlig identische Ergebnisse.

Literatur