Phänomen JavaScript

JavaScript ist aus dem Stadium einer kleinen Script-Sprache raus, mit der nur HTML-Seiten aufgehübscht werden. Mit JavaScript können ernsthafte Anwendungen entwickelt werden, die auf verschiedenen Devices und sogar auf dem Server laufen.

Dabei hat JavaScript viele Gesichter bzw. Programmier-Paradigmen. Klassisch prozedural, objektorientiert  und Anleihen aus der Funktionalen Programmierung. Insbesondere letztere hat mich begeistert und inspiriert.

Asynchron und Callbacks

javascriptBei der Funktionalen Programmierung stehen Funktionen im Zentrum. Salop gesagt heißt das, man gibt an, was ein Programm tun soll und nicht wie es etwas tun soll. Dies geschieht über Funktionsaufrufe. Die Denkweise dabei ist „tue folgendes und wenn Du damit fertig bist, dann mache dies mit dem Ergebnis“. Das Sprachmittel dabei sind Callbacks. Einer Funktion A wird als Aufrufparamter eine Funktion B übergeben, die von A aufgerufen wird, meist wenn sie mit der eigenen Arbeit fertig ist. Dadurch sieht der Quellcode auch etwas anders aus, als man es klassisch kennt. Funktionsaufrufe, deren Rückgabewert einer Variablen zugewiesen wird, gibt es nicht.
Folgendes Beispiel soll dies veranschaulichen: Es soll eine Zahl um den Wert eins hoch gezählt und das Ergebnis auf der Konsole ausgegeben werden. Dazu wird je eine Funktion bereit gestellt.
Klassisch prozedural sieht das wie folgt aus:

function increment(x) {
   return x+1;
}

function log(value) {
   console.log(value);
}

function doIt() {
   var y = increment(2);
   log(y);   // Ausgabe 3
   // do something else
}

Im funktionalen Stil sieht das folgendermaßen aus:

function increment(x, callback) {
   callback(x+1);
}

function log(value) {
   console.log(value);
}

function doIt() {
   increment(2, log);
   // do something else
}

Der Funktion increment wird die Funktion log als Parameter mitgegeben. increment ruft  log auf mit dem Ergebnis der eigenen Verarbeitung.
Wozu das Ganze? In der prozeduralen Programmierung wird erst increment aufgerufen, dann log und dann kommt „do something else“. Alles schön sequentiell der Reihe nach. Dauert die Verarbeitung in increment sehr lange, ist der weitere Programmverlauf blockiert, „do something else“ wird erst ausgeführt, wenn increment fertig ist. Im funktionalen Stil kann die Verarbeitung innerhalb von increment asynchron erfolgen. Das heißt nach dem Aufruf von increment geht die Verarbeitung mit „do something else“ sofort weiter. log wird erst aufgerufen, wenn increment mit seiner Arbeit fertig ist.
So kann increment z.B. per AJAX asynchron einen Service aufrufen und sobald die Antwort da ist, diese per log ausgeben. Solange auf die Antwort vom Service gewartet wird, kann mit „do something else“ weiter gemacht werden. So ist es z.B. möglich, dass ein UI nicht blockiert wird, solange eine längere Verarbeitung läuft. Dieses Feature nutze ich in meinem RSS-Reader beim aktualisieren der RSS-Feeds. Sobald der erste Feed geladen ist, kann er im UI ausgewählt und gelesen werden, während die anderen Feeds noch laden.

Für das obige Beispiel gib es in JavaScript noch eine Kurzform. Es kann eine anonyme Funktion als Parameter mitgegeben werden. Dabei wird nicht der Funktionsname angegeben, sondern die Funktion selbst als Parameter hinterlegt:

function increment(x, callback) {
   callback(x+1);
}

function doIt() {
   increment(2, function(value) {
      console.log(value);
   });
}

Da JavaScript Single Threaded ist, ist asynchrone Verarbeitung extrem wichtig für die Skalierbarkeit. In einem einzelnen Thread können nicht viele Dinge parallel verarbeitet werden, sondern immer nur eines nach dem anderen. Die Asynchronität sorgt für Parallelität.
Extremen Gebrauch davon macht node.js, eine schlanke JavaScript Plattform für den Server. In dieser Plattform wird fast alles  asynchron verarbeitet. Wenn ein Request rein kommt, wird er asynchron verarbeitet und es kann sofort der nächste Request entgegen genommen werden. Die Antwort an den Client erfolgt dann in einem Callback, sobald die Verarbeitung fertig ist. Das ist besonders interessant, wenn langsame Back Ends aufgerufen werden müssen. Solange auf die Antwort gewartet wird, ist der Thread nicht blockiert und kann sofort wieder einen Request entgegen nehmen. Probleme, wie sie von Java Application Server bekannt sind, bei denen in so einem Szenario schnell der ganze ThreadPool mit lauter, auf Back End Systeme, wartende Threads blockiert ist und der Server nichts tut und trotzdem keine Anfragen mehr entgegen nehmen kann, kommen so nicht vor. Um das zu ermöglichen nutzt node.js spezielle Betriebssystem I/O Features und ist somit besonders geeignet bei intensivem I/O, nicht jedoch bei CPU intensiven Operationen.

Seit HTML5 gibt es ein neues JavaScript Feature: Web Worker. Damit können neue Threads innerhalb von JavaScript für die parallele Verarbeitung aufgemacht werden. Dauert eine Verarbeitung lange, ohne das asynchron ein AJAX Call durchgeführt wird, kann die Arbeit in einen Web Worker ausgelagert werden. Sobald dieser mit der Arbeit fertig ist, wird der Callback aufgerufen.

JavaScript und Patterns

Bei der Entwicklung größerer Anwendungen sind Patterns extrem hilfreich um den Code sauber zu strukturieren.

Namespaces

Ganz nach dem Vorbild von Java können auch in JavaScript Namespaces festgelegt werden um Namenskonflikte zu vermeiden.
Das folgende Beispiel legt einen Namespace für den RSS-Reader an.

var de = de || {};
de.discoveration = de.discoveration || {};
de.discoveration.rssreader = de.discoveration.rssreader || {};

Der Namespace wird nach der Schreibweise var de = de || {} angelegt. Dabei wird  verhindert, dass eine eventuell bereits vorhandene Variable de überschrieben wird.

Kapselung durch Closures

In JavaScript gibt es kein private oder public wie in Java. Um Daten und Funktionen zu kapseln werden Closures verwendet. Von einem Closure spricht man, wenn eine äußere Funktion aufgerufen wird, die eine innere Funktion zurück gibt, die auf die Variable der äußeren Funktion zugreift. Die Variable ist dabei auch dann noch verfügbar, wenn die äußere Funktion beendet wird, denn die innere, zurückgegebene Funktion schließt die Variable ein, deshalb Closure. Dadurch können quasi verschiedene Zustände gespeichert werden.

function counter() {
   var count = 0;
   return function increment() {
      count++;
      console.log(count);
   }
}

var counter1 = counter();
counter1();   // Ausgabe ist 1
counter1();   // Ausgabe ist 2
var counter2 = counter();
counter2();   // Ausgabe ist 1
counter2();   // Ausgabe ist 2
counter2.count;   // undefined da count nicht sichtbar ausserhalb des Closures

Module

Module können ebenso mit JavaScript erstellt werden. Dabei werden auch über Closures Daten und Funktionen gekapselt. Zusammen mit den Namespaces ist es somit möglich, Module mit öffentlichen und privaten Funktionen zu definieren, ganz so wie man es aus Java kennt.
Dabei kommt das IIFE-Entwurfsmuster (Immediately-Invoked Function Expression) zum Einsatz. Bei diesem Muster werden Funktionen unmittelbar, nachdem sie definiert wurden, aufgerufen.

var de = de || {};
de.discoveration = de.discoveration || {};
de.discoveration.TestModule = (function() {
   // private attribute
   var greeting = "Hallo ";

   // private function
   function sayHello(name) {
      var text = greeting + name;
      log(text);
   }

   // private function
   function log(msg) {
      console.log(msg);
   }

   // public API
   return {
      sayHello: sayHello   // reference on private function
   }
})();

de.discoveration.TestModule.sayHello("Marco");

Fazit

Mit JavaScript ist inzwischen so einiges möglich, das weit über das aufhübschen von HTML Seiten geht. Ganze Anwendungen können gebaut werden, auf dem Client und auf dem Server. Die Anwendungen sind dank Asynchronität schlank und skalierbar und durch Patterns entsteht auch gut strukturierter Code.
Der Code ist jedoch im ersten Blick gewöhnungsbedürftig, insbesondere durch Callbacks nicht einfach zu lesen und verständlich wie ein Buch. Auch die Tatsache, dass alles Funktionen sind und auch Funktionen als Parameter übergeben werden oder Funktionen Variablen zugewiesen werden können macht den Code schwerer verständlich, zumindest für einen langjährigen Java Entwickler wie mich. JavaScript ist nicht so komfortabel wie es eine hohe Sprache wie Java ist.
Bisher habe ich auch noch keine richtig gute IDE gefunden die mit Code-Completion die Entwicklung vereinfacht. Eclipse kommt auch mit den Entwurfsmustern nicht zurecht und zeigt in der Outline View die einzelnen Funktionen in Modulen nicht an.
Trotzdem komme ich zu der Meinung, dass JavaScript das neue Java ist und Java das neue Cobol. Java ist legacy geworden, dick, fett für große Enterprise Anwendungen. Im Back End die richtige Wahl. JavaScript ist die richtige Wahl für schlanke dynamische Cross-Device Anwendungen, für Web Apps. Auch der Serverteil kann dank node.js in JavaScript entwickelt werden.
Allerdings ist JavaScript, meiner Ansicht nach, etwas für absolute Experten. Es benötigt Übung um damit gute Anwendungen zu bauen.

Ist JavaScript Enterprise ready? Nicht im Breiteneinsatz, aber durchaus für spezielle Anwendungen, die von Cracks entwickelt werden. JavaScript ist erwachsen geworden, allerdings muss es aufpassen, damit es seine Jugend bewahrt.

Schreibe einen Kommentar

Anmelden um einen Kommentar abzugeben.

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

*