Einführung in objektorientiertes JavaScript

Im Kern ist JavaScript eine objektorientierte Programmiersprache mit mächtigen und flexiblen Fähigkeiten der OOP. Dieser Artikel beginnt mit einer Einführung in die objektorientierte Programmierung. Im zweiten Teil wird das Objektmodel von JavaScript erläutert. Danach folgt eine praktische Vorstellung der objektorientierten Programmierung in JavaScript. Dieser Artikel beschreibt nicht die neuere Syntax für objektorientierte Programmierung in ECMAScript 6.

JavaScript Review

Wenn Sie nicht mit den Konzepten in JavaScript, wie zum Beispiel Variablen, Typen, Funktionen und Geltungsbereich, vertraut sind, lesen Sie bitte zuerst den Artikel Eine Wiedereinführung in JavaScript. Sie können ebenfalls auf den JavaScript Guide zurückgreifen.

Objektorientierte Programmierung

Die objektorientierte Programmierung ist ein Paradigma, in dem über Abstraktion Modelle basierend auf der realen Welt erstellt werden. Es verwendet Techniken, die von zuvor etablierten Paradigmen eingeführt wurden. Darunter befinden sich Modularität, Polymorphismus und Datenkapselung. Heutzutage unterstützen viele populäre Programmiersprachen (z.B.: Java, JavaScript, C#, C++, Python, PHP, Ruby und Objective-C) die objektorientierte Programmierung (OOP).

Objektorientierte Programmierung kann als das Design einer Software unter der Verwendung von kooperierenden Objekten angesehen werden. Die traditionelle Ansicht dagegen sieht ein Programm als eine Menge von Funktionen oder noch einfacher als eine Abfolge von Instruktionen an den Computer. In der OOP kann jedes Objekt Nachrichten empfangen, Daten verarbeiten und Nachrichten an andere Objekte senden. Jedes Objekt kann als eine unabhängige kleine Maschine mit einer bestimmten Rolle oder Verantwortlichkeit angesehen werden.

Die Objektorientierte Programmierung versucht höhere Flexibilität und Wartbarkeit in Programmen zu ermöglichen und wird deshalb oft beim Software Engineering von hochskalierenden Programmen eingesetzt. Durch die starke Konzentration auf Modularität hat objektorientierter Code den Vorteil, dass er einfacher zu entwickeln und zu verstehen ist. Sie konzentriert sich direkter auf die Analyse, die Codierung und das Verstehen von komplexen Situationen und Prozeduren als weniger modulare Programmiermethoden.1

Begriffsdefinitionen

Namespace
Ein Container, der Entwicklern die Bündelung von Funktionalität unter einem eindeutigen, applikationsspezifischen Namen ermöglicht.
Klasse
Definiert die Eigenschaften eines Objekts. Sie ist die Definition einer Vorlage von Variablen und Methoden eines Objekts.
Objekt
Eine Instanz einer Klasse.
Eigenschaft
Eine Eigenschaft eines Objekts, wie zum Beispiel eine Farbe.
Methode
Eine Fähigkeit eines Objekts, wie zum Beispiel laufen. Sie ist eine Unterroutine oder Funktion, die mit der Klasse assoziiert ist.
Konstruktor
Eine spezielle Methode, die zur Instanziierung eines Objekts aufgerufen wird. Sie hat gewöhnlicherweise denselben Namen wie die Klasse, zu der sie gehört.
Vererbung
Eine Klasse kann die Eigenschaften und Fähigkeiten von anderen Klassen erben und somit für sich verwenden.
Datenkapselung
Die Datenkapselung ist eine Technik, um die Daten und die Methoden, die diese Daten verwenden, miteinander zu bündeln.
Abstraktion
Die Verknüpfung von komplexer Vererbung, Methoden und Properties eines Objekts müssen ein Modell der Realität simulieren können.
Polymorphismus
Poly bedeute "viele"  und Morphismus bedeutet "Formen". Verschiedene Klassen können dieselbe Methode oder dieselbe Eigenschaft definieren.

Lesen Sie bitte den Artikel Objektorientierte Programmierung auf Wikipedia für eine umfassendere Beschreibung der objektorientierten Programmierung.

Prototypenbasierte Programmierung

Prototypenbasierte Programmierung ist eine Art der objektorientierten Programmierung bei der keine Klassen gegenwärtig sind. Die Wiederverwendung von Verhalten (bekannt als Vererbung in klassenbasierten Programmiersprachen) wird über das Dekorieren von existierenden Objekten erreicht, die als Prototyp dienen. Dieses Modell wird als klassenlose, prototyporientierte oder instanzorientierte Programmierung bezeichnet.

Das ursprüngliche (und am meisten kanonische) Beispiel einer prototypenbasierten Programmiersprache ist Self (Programmiersprache), welche von David Ungar and Randall Smith entwickelt wurde. Seitdem wurde die klassenlose Programmierung immer mehr populär und wurde für andere Programmiersprachen wie JavaScript, Cecil, NewtonScript, Io, MOO, REBOL, Kevo, Squeak (wenn das Viewer Framework zur Manipulation von Morphic Komponenten verwendet wird) und einige andere adoptiert.2

Objektorientierte Programmierung mit JavaScript

Namespace

Ein Namespace ist ein Container in dem Entwickler Funktionalitäten unter einem eindeutigen, applikationsspezifischen Namen zusammenfassen können. In JavaScript ist ein Namespace ein gewöhnliches Objekt, welches Methoden, Eigenschaften und Objekte enthält.

Im Gegensatz zu manchen anderen objektorientierten Programmiersprachen gibt es in der Sprachebene von JavaScript keinen Unterschied zwischen einem regulären Objekt und einem Namespace.

Die Idee hinter der Erstellung eines Namespaces in JavaScript ist simpel: es wird ein globales Objekt erstellt, welches alle Variablen, Methoden und Funktionen als Eigenschaften besitzt. Zusätzlich kann die Verwendung von Namespaces Namenskonflikten in der Applikation vorbeugen.

Es wird ein globales Objekt names MYAPP erstellt:

// global namespace
var MYAPP = MYAPP || {};

Im obigen Code wird zuerst geprüft, ob MYAPP bereits definiert wurde (entweder in derselben oder einer anderen Datei). Wenn MYAPP bereits definiert wurde, wird das globale Objekt MYAPP verwendet. Anderenfalls wird ein leeres Objekt names MYAPP erstellt, welches später Methoden, Funktionen, Variablen und andere Objekte kapseln kann.

Innerhalb eines Namespaces können weitere Namespaces erstellt werden:

// sub namespace
MYAPP.event = {};

Der folgende Code erstellt einen Namespace und fügt diesem Variablen, Funktionen und Methoden hinzu:

// Create container called MYAPP.commonMethod for common method and properties
MYAPP.commonMethod = {
  regExForName: "", // define regex for name validation
  regExForPhone: "", // define regex for phone no validation
  validateName: function(name){
    // Do something with name, you can access regExForName variable
    // using "this.regExForName"
  },
 
  validatePhoneNo: function(phoneNo){
    // do something with phone number
  }
}

// Object together with the method declarations
MYAPP.event = {
    addListener: function(el, type, fn) {
    //  code stuff
    },
   removeListener: function(el, type, fn) {
    // code stuff
   },
   getEvent: function(e) {
   // code stuff
   }
  
   // Can add another method and properties
}

//Syntax for Using addListner method:
MYAPP.event.addListener("yourel", "type", callback);

Standardmäßig eingebaute Objekte

JavaScript besitzt verschiedene Objekte im Kern. Darunter befinden sich zum Beispiel die Objekte Math, Object, Array, und String. Das folgende Beispiel zeigt, wie das Math Objekt mit der random() Methode eine Zufallszahl generiert.

console.log(Math.random());
Hinweis: Dieses und folgende Beispiele setzen voraus, dass console.log() global definiert wurde. Die console.log Funktion ist kein eigentlicher Sprachteil von JavaScript, jedoch implementieren viele Browser diese zum Debuggen.

Siehe JavaScript Reference: Standard built-in objects für eine List aller Objekte im Kern von JavaScript.

Jedes Objekt in JavaScript ist eine Instanz des Objekts Object und erbt somit alle Eigenschaften und Methoden von diesem.

Eigene Objekte

Die Klasse

JavaScript ist eine prototypenbasierte Programmiersprache, welche im Gegensatz zu C++ oder Java keine class Anweisung enthält. Dies kann für Programmierer manchmal verwirrend sein, wenn sie normalerweise Programmiersprachen mit einer class Anweisung verwenden. JavaScript verwendet stattdessen Funktionen als Klassen. Die Definition einer Klasse erfolgt analog zu der einer Funktion. Im folgenden Beispiel wird eine neue Klasse namens Person definiert.

var Person = function(){ }

Das Objekt (Instanz einer Klasse)

Um eine neue Instanz eines Objekts obj zu erstellen, wird die Anweisung new obj ausgeführt. Das Ergebnis der Anweisung (welches den Typ obj hat) wird für eine spätere Verwendung einer Variablen zugewiesen.

Im folgenden Beispiel wird eine Klasse mit dem Namen Person definiert und zwei Objekte (person1 and person2) instanziiert.

var person1 = new Person();
var person2 = new Person();
Lesen Sie bitte Object.create() für eine neue, alternative Methode zur Objektinstanziierung.

Der Konstruktor

Der Konstruktor wird zum Zeitpunkt der Instanztierung (wenn eine Objektinstanz erstellt wird) aufgerufen. Er ist als eine Methode der Klasse definiert. In JavaScript agiert die Funktion als Konstruktor für das Objekt. Somit muss keine Methode explizit für den Konstruktor definiert werden. Jede deklarierte Anweisung in der Klasse wird zum Zeitpunkt der Instanziierung ausgeführt.

Der Konstruktor wird zur Initialisierung der Eigenschaften oder vorbereitende Methodenaufrufe zur Verwendung des Objekts verwendet. Das Hinzufügen von Klassenmethoden und deren Definitionen verwendet eine andere Syntax, welche später im Artikel erläutert wird.

Im folgenden Beispiel loggt der Konstruktor der Klasse Person eine Nachricht, wenn ein Person Objket instanziiert wird.

function Person() {
  console.log('Instanz erstellt');
}

var person1 = new Person();
var person2 = new Person();

Die Eigenschaft (Objektattribut)

Eigenschaften sind Variablen innerhalb einer Klasse. Jede Instanz des Objekts besitzt diese Eigenschaften. Eigenschaften sollten im Prototyp der Eigenschaft in der Klasse (Funktion) gesetzt werden, damit die Vererbung korrekt funktioniert.

Eigenschaften können innerhalb eines Objekts mit dem Schlüsselwort this, welches das aktuelle Objekt referenziert, verwendet werden. Der Zugriff (lesen oder schreiben) auf eine Eigenschaft von außerhalb der Klasse wird durch die Syntax InstanceName.Property ermöglicht. Diese Syntax ist analog zu C++, Java und zahlreichen anderen Programmiersprachen. Innerhalb der Klasse wird die Syntax this.Property zum Lesen oder Schreiben des Werts verwendet.

Im folgenden Beispiel wird die Eigenschaft firstName für die Person Klasse definiert. Bei der Instanziierung wird ihr ein Wert zugewiesen.

function Person(firstName) {
  this.firstName = firstName;
  console.log('Person instantiated');
}

var person1 = new Person('Alice');
var person2 = new Person('Bob');

// Zeigt die Eigenschaft firstName des Objektes
console.log('person1 is ' + person1.firstName); // logs "person1 is Alice"
console.log('person2 is ' + person2.firstName); // logs "person2 is Bob"

Die Methoden

Methoden befolgen beinahe dieselbe Logik wie Eigenschaften mit dem Unterschied, dass sie Funktionen sind und deshalb als Funktionen definiert werden. Der Aufruf einer Funktion erfolgt analog zu Eigenschaften gefolgt von () am Ende des Methodennames. Innerhalb der Klammern können der Methode Argumente übergeben werden. Um eine Methoden zu definieren, muss eine Funktion einer benannten Eigenschaft der prototype Eigenschaft der Klasse zugewiesen werden. Der Name der benannten Eigenschaft dient nun als Methodennamen über den die Methode auf dem Objekt aufgerufen werden kann.

Im folgenden Beispiel wird die Method sayHello() für die Person Klasse definiert.

function Person(firstName) {
  this.firstName = firstName;
}

Person.prototype.sayHello = function() {
  console.log("Hello, I'm " + this.firstName);
};

var person1 = new Person("Alice");
var person2 = new Person("Bob");

// Aufrufen der Methode sayHello der Person.
person1.sayHello(); // logs "Hello, I'm Alice"
person2.sayHello(); // logs "Hello, I'm Bob"

Methoden in JavaScript sind gewöhnliche Funktionsobjekt, die als eine Eigenschaft an ein Objekt gebunden werden. Dadurch können Methoden auch "außerhalb des Kontexts" aufgerufen werden. Das folgende Beispiel veranschaulicht dies:

function Person(firstName) {
  this.firstName = firstName;
}

Person.prototype.sayHello = function() {
  console.log("Hello, I'm " + this.firstName);
};

var person1 = new Person("Alice");
var person2 = new Person("Bob");
var helloFunction = person1.sayHello;

// logs "Hello, I'm Alice"
person1.sayHello();

// logs "Hello, I'm Bob"
person2.sayHello();

// logs "Hello, I'm undefined" (oder schlägt
// mit einem TypeError im strict mode fehl)
helloFunction();                                    

// logs true
console.log(helloFunction === person1.sayHello);

// logs true
console.log(helloFunction === Person.prototype.sayHello);

// logs "Hello, I'm Alice"
helloFunction.call(person1);

Das Beispiel zeigt, dass alle Referenzen zur sayHello Funktion — die auf person1, auf Person.prototype, in der helloFunction Variable, etc. — dieselbe Funktion referenzieren. Der Wert von this während eines Funktionsaufrufs hängt von der Art ab, wie die Funktion aufgerufen wird. In allgemeinen Fällen, wenn die Funktion in einem Ausdruck  über die Eigenschaft aufgerufen wird — person1.sayHello() — referenziert this das Objekt, auf der die Funktion aufgerufen wird (person1). Deshalb verwendet person1.sayHello() den Namen "Alice" und person2.sayHello() den Namen "Bob". Wenn die Funktion über andere Arten aufgerufen wird, ist this unterschiedlich definiert: Der Aufruf von der Variablen aus — helloFunction() — setzt this auf das globale Objekt (window, in Webbbrowsern). Da dieses Objekt (wahrscheinlich) keine firstName Eigenschaft besitzt, gitb die Funktion "Hello, I'm undefined" aus. (Dieses Ergebnis wird im Loose Mode produziert. Im Strict Mode verhält sich die Funktion anders und es könnte ein Fehler ausgelöst werden. Um Verwirrungen zu vermeiden wird dies nicht weiter im Detail besprochen) this kann auch explizit über Function#call (oder Function#apply) definiert werden. Die geschieht in der letzten Zeile des obigen Beispiels.

Hinweis: Mehr über this können Sie unter Function#call und Function#apply finden.

Vererbung

Vererbungs ist eine Methode, um eine Klasse zu erstellen, die eine spezialisierte Version von einer (Einfachvererbung) oder mehrerer (Mehrfachvererbung) Klassen ist (JavaScript unterstützt nur Einfachvererbung). Die spezialisierte Klasse wird normalerweise als child bezeichnet. Die Klasse, von der geerbt wird, wird als parent bezeichnet. Vererbung in JavaScript funktioniert indem eine Instanz der parent Klasse der child Klasse zugewiesen und dann spezialisiert wird. In modernen Webbrowsern kann die Vererbung auch mittels der Verwendung von Object.create implementiert werden.

Hinweis: JavaScript erkennt den Konstruktor prototype.constructor (siehe Object.prototype) der child Klasse nicht. Er muss manuell angegeben werden. Sehe dazu zusätzlich den Beitrag "Why is it necessary to set the prototype constructor?" (englisch) auf Stackoverflow an.

Im folgenden Beispiel wird die Klasse Student als child Klasse von Person definiert. Dann wird die Methode sayHello() neu definiert und die Methode sayGoodBye() zur Klasse Person hinzugefügt.

// Definiert den Person Konstruktor
function Person(firstName) {
  this.firstName = firstName;
}

// Fügt Methoden zum Person.prototype hinzu
Person.prototype.walk = function(){
  console.log("I am walking!");
};
Person.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName);
};

// Definiert den Student Konstruktor
function Student(firstName, subject) {
  // Call the parent constructor, making sure (using Function#call)
  // that "this" is set correctly during the call
  Person.call(this, firstName);

  // Initialize our Student-specific properties
  this.subject = subject;
};

// Erstellt ein Student.prototype Objekt das von Person.prototype erbt.
// Hinweis: Ein häufiger Fehler ist der Einsatz von "new Person()" beim erstellen vomeines
// Student.prototype. Das ist falsch aus einigen Gründen, nicht nur 
// das wir keinen Parameter der Person für "firstName" mitgeben können. 
// Der korrekte Ort für den Aufruf von Person ist oben, wo wir es 
// von Student aufrufen.
Student.prototype = Object.create(Person.prototype); // See note below

// Setzt die "constructor" Eigenschaft um auf Student zu referenzieren.
Student.prototype.constructor = Student;

// Ersetzt die "sayHello" Methode
Student.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName + ". I'm studying "
              + this.subject + ".");
};

// Fügt die "sayGoodBye" Methode hinzu
Student.prototype.sayGoodBye = function(){
  console.log("Goodbye!");
};

// Beispieleinsatz:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello();   // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk();       // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"

// Check that instanceof works correctly
console.log(student1 instanceof Person);  // true 
console.log(student1 instanceof Student); // true

Unter Betrachtung der Zeile Student.prototype = Object.create(Person.prototype);: In älteren JavaScript Engines ohne Object.create, kann ein "polyfill" (auch "shim", siehe den verlinkten Artikel) oder eine Funktion, die dasselbe Ergebnis erzielt, verwendet werden:

function createObject(proto) {
    function ctor() { }
    ctor.prototype = proto;
    return new ctor();
}

// Einsatz:
Student.prototype = createObject(Person.prototype);
Lesen Sie Object.create, um mehr zum obigen Beispiel zu erfahren.

Stellen sie sicher, dass this zum richtigen Objekt referenziert unabhängig davon, wie das Objekt erstellt wird. Es gibt eine einfache Methode dieses sicherzustellen:

var Person = function(firstName) {
  if (this instanceof Person) {
    this.firstName = firstName
  } else {
    return new Person(firstName)
  }
}

Datenkapselung

Im obigen Beispiel muss die Student Klasse nicht wissen, wie die Methode walk() der Person Klasse implementiert ist, um sie verwenden zu können. Die Student Klasse muss die Methode nicht explizit definierten, solange Sie unverändert übernommen werden soll. Dabei handelt es sich um Datenkapselung, bei der jede Klasse ihre Daten und Methoden in einer Einheit bündeln.

Information Hiding ist ein gängiges Feature in anderen Programmiersprachen, bei dem Methoden und Eigenschaften als privat oder geschützt definierten werden können. Obwohl Information Hiding mit JavaScript realisiert werden kann, ist es keine Voraussetzung für objektorientierte Programmierung.3

Abstraktion

Abstraktion ist ein Mechanismus, der die Modellierung eines Teils der Problemstellung erlaubt. Dazu kann Vererbung (Spezialisierung) oder Komposition eingesetzt werden. JavaScript ermöglicht die Spezialisierung mit Vererbung. Komposition wird realiseirt, indem Instanzen einer Klasse als Werte von Eigenschaften eines Objekts gesetzt werden.

Die Function Klasse in JavaScript erbt von der Object Klasse (Spezialisierung). Die Function.prototype Eigenschaft ist eine Instanz der Object Klasse (Komposition).

var foo = function(){};

// logs "foo is a Function: true"
console.log('foo is a Function: ' + (foo instanceof Function));

// logs "foo.prototype is an Object: true"
console.log('foo.prototype is an Object: ' + (foo.prototype instanceof Object));

Polymorphismus

Wie alle Methoden und Eigenschaften innerhalb der prototype Eigenschaft definiert werden, können unterschiedliche Klassen Methoden mit demselben Namen definieren. Methoden sind im Scope der Klasse, in der sie definiert sind, verfügbar. Das gilt solange die Klassen keine parent-child Beziehung besitzen (wenn die eine Klasse nicht innerhalb einer Kette von der anderen Klasse erbt).

Anmerkungen

Die im Artikel präsentierten Techniken umfassen nicht alle Möglichkeiten der objektorientierten Programmierung in JavaScript, da JavaScript sehr flexibel in der Umsetzung der objektorientierten Programmierung ist.

Die vorgestellten Techniken zeigen nicht alle verfügbaren Hacks in JavaScript und mimen nicht die Implementierung der Objektorientiertheit in anderen Programmiersprachen.

Es gibt weitere Techniken, welche eine fortgeschrittenere objektorientierte Programmierung in JavaScript ermöglichen. Diese gehen aber über den Umfang dieses einführenden Artikels hinaus.

Referenzen

  1. Wikipedia. "Object-oriented programming" und "Objektorientierte Programmierung"
  2. Wikipedia. "Prototype-based programming" und "Prototypenbasierte Programmierung"
  3. Wikipedia. "Encapsulation (object-oriented programming)" und "Datenkapselung (Programmierung)"

Siehe auch

Schlagwörter des Dokuments und Mitwirkende

 Zuletzt aktualisiert von: BurnerPat,