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());
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();
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.
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.
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);
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
- Wikipedia. "Object-oriented programming" und "Objektorientierte Programmierung"
- Wikipedia. "Prototype-based programming" und "Prototypenbasierte Programmierung"
- Wikipedia. "Encapsulation (object-oriented programming)" und "Datenkapselung (Programmierung)"