Das Unternehmen, für das ich arbeite, entwickelt derzeit verschiedene JavaScript-Webanwendungen mit jQuery, Backbone und Underscore. Wir schreiben diese hauptsächlich in der Sprache CoffeeScript, die nach JavaScript kompiliert wird.
Ich möchte diese Software-Toolchain im Folgenden kritisch diskutieren und Vor- und Nachteile aufzeigen. Ich halte die besagten Bibliotheken für nicht oder nur bedingt standardkonform und möchte schließlich für zukunftsfähige Bibliotheken plädieren.
jQuery
jQuery ist der De-facto-Standard für DOM-Scripting, Animationen und HTTP-Kommunikation (»Ajax«). Mit jQuery lassen sich die üblichen DOM-Aufgaben mit kurzer und sprechender Syntax umsetzen.
jQuery war stets nur eine Helferbibliothek, die einem jenseits des Patterns »Wrapper-Liste mit DOM-Elementen« nichts zur übergreifenden Organisation bietet. jQuery verzichtet bewusst darauf, einen Programmierstil vorzuschreiben, und beschränkt sich darauf, Operationen auf DOM-Knotenlisten zu vereinfachen. Andere Frameworks speziell für clientseitige Webanwendungen wie YUI, Dojo und Ext.JS hatten schon immer einen umfassenderen Anspruch.
jQuery ist auf dem Siegeszug und hat Konkurrenten wie Prototype und Mootools den Rang abgelaufen. Auch wenn es sich auf einen Kernbereich des clientseitigen JavaScripts beschränkt, handelt es sich um ein riesiges, stetig wachsendes JavaScript, von dem man in der Regel nur wenige Teile verwendet. Besonders aus der Ecke der Micro-Frameworks erntet jQuery als monolithische Software Kritik. Zur Verteidigung wird die testgetriebene Entwicklung sowie die Browserkompatibilität angeführt.
Underscore
Underscore ist eine kleine eigenständige Bibliothek, die häufig im Zusammenhang mit jQuery eingesetzt wird. Sie bietet vieles, was jQuery auslässt; sie vereinfacht die JavaScript-Programmierung selbst. Underscore erlaubt einen komfortablen Umgang mit Objekten/Hashes, Listen und Funktionen, wie man es in Sprachen wie Python, Ruby sowie in der funktionalen Programmierung gewöhnt ist. Insofern sind seine Features unverzichtbar für die fortgeschrittene JavaScript-Programmierung.
Ähnliche Helferlein bieten Prototype und Mootools durch prototypische Erweiterung der Kernobjekte. In der Philosophie ähnelt Underscore jQuery: Alle Funktionalität ist im Namespace _
bzw. underscore
gekapselt. Das heißt, man muss immer _.methode(…)
oder _(…).methode(…)
schreiben, um die Underscore-Funktionen zu nutzen.
Backbone
Backbone soll ein einfach einsetzbares, an das Model-View-Controller-Pattern angelehntes Framework für Webanwendungen sein. Es gibt Models und Collections von Models, ferner gibt es Views sowie Router (vormals Controller). Backbone stammt vom selben Autor wie Underscore und ist auf Underscore angewiesen. jQuery, Underscore und Backbone sind ein häufiges Team.
Viele Ideen sind brauchbar und an anderer Stelle bewährt, z.B. bei serverseitigen Web-Frameworks wie Ruby on Rails. Während sich jedoch bei Rails starke Konventionen ausgeprägt haben, was unter dem Motto »Convention over Configuration« als eine seiner Stärken gilt, so gibt es bei Backbone bisher wenig Anleitung. Das MVC-Konzept ist unklar und m.E. nicht ausgereift. Die Beispiele sind eher schlecht und Best Practises für größere Anwendungen scheinen noch zu fehlen.
Die Backbone-Anwendungen, die wir geschrieben haben, unterscheiden sich in der Architektur stark. Das heißt, man muss viele Konventionen wie etwa Separation of Concerns selbst aufstellen und etablieren. Um Wiederholungen zu vermeiden, muss man sich selbst wiederverwendbare Grundmodule schaffen und landet so schnell bei einer stattlichen Klassenhierarchie.
CoffeeScript
CoffeeScript ist eine alternative Syntax für JavaScript, die einem vieles vereinfacht. Die Schreibweise soll übliche Fehler verhindern, ist durch massig Syntactic Sugar sowie Significant Whitespace weitaus übersichtlicher und kompakter. Zudem wird trotz der guten Lesbarkeit relativ performanter Code erzeugt. Essentielle Features wie Function-Binding sind direkt in die Syntax integriert. CoffeeScript bietet fernen »Klassen«. Diese versuchen nicht, fremde Konzepte zu JavaScript zu bringen, sondern sind bloß eine konventionelle, verständliche und performante Anwendung von Konstruktoren, Prototypen und prototypischer Delegation (Vererbung).
CoffeeScript vereinfacht vieles, ist aufgrund seiner freien, flexiblen Syntax aber auch sehr fragil. Häufig muss man den kompilierten JavaScript-Code prüfen und muss beurteilen können, ob das gewünschte herausgekommen ist. CoffeeScript ist bewusst einseitig und versucht einen bestimmten Programmierstil zu forcieren, der sich an »JavaScript: The Good Parts« von Douglas Crockford anlehnt. Manches lässt sich in CoffeeScript einfacher lösen, anderes schwieriger. Selbst CoffeeScript-Befürworter sind der Meinung, dass man JavaScript gut beherrschen sollte, bevor man sich an CoffeeScript wagt. CoffeeScript nimmt einem nur begrenzt ab, die Funktionsweise der Sprache verstehen zu müssen: Unter der Zuckerschicht liegt ganz normales JavaScript.
CoffeeScript + jQuery + Underscore = Tohuwabohu
Nun geht es mir nicht nur darum, die einzelnen Komponenten dieser Werkzeugkette vorzustellen. Ich bin mit dieser Toolchain unzufrieden, weil sie keinem übergreifenden Konzept folgt und die einzelnen Teile nicht ineinander greifen, sondern Funktionalität duplizieren. Dazu einige Beispiele:
ECMAScript 3 | for (var index = 0, l = array.length, item; index < l; index++) { item = array[index]; item.foo(index) } |
---|---|
CoffeeScript | item.foo(index) for item, index in array Kompiliert zu: var index, item, _len; for (index = 0, _len = array.length; index < _len; index++) { item = array[index]; item.foo(index); } |
Underscore in CoffeeScript | _.each array, (item, index) -> item.foo(index) |
jQuery in CoffeeScript | $.each array, (index, item) -> item.foo(index) |
ECMAScript 5 in CoffeeScript | array.forEach (item, index) -> item.foo(index) |
ECMAScript 3 (eine einfache Möglichkeit, eine Closure zu erzeugen) | var instance = this; $('#elem').click(function () { instance.method(); }) Zur Erklärung siehe Organisation von JavaScripten: Objektverfügbarkeit und this-Kontext |
---|---|
CoffeeScript | $('#elem').click => @method() Kompiliert zu: var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; $('#elem').click(__bind(function() { return this.method(); }, this)); |
Underscore in CoffeeScript | $('#elem').click _.bind(@method, @) |
jQuery in CoffeeScript | $('#elem').click $.proxy(@method, @) |
ECMAScript 5 in CoffeeScript | $('#elem').click @method.bind(@) |
Bei der gleichzeitigen Verwendung von CoffeeScript, jQuery und Underscore hat man keine Standardlösungen, da alle Bibliotheken in sich geschlossen sind. Es gibt nicht eine richtige, standardisierte Umsetzung, sondern mehrere äquivalente. Es fehlt der übergreifende Kitt, der die verschiedenen Ansätze zusammenbringt und vereinigt.
Welche der obigen Lösungen sollte man wählen? Ich würde zunächst für CoffeeScript plädieren, da das die primäre Sprache ist, in der man schreibt. Letztlich erzeugt Binding in CoffeeScript aber eine redundante Helferfunktion, welche nicht die standardisierte bind
-Methode aus ECMAScript 5 verwendet (siehe Diskussion auf Github), falls die JavaScript-Engine diese nativ unterstützt.
Grundlagen für zukünftiges JavaScript
Bereits Anfang 2010 hatte ich gefragt: Wo bleiben neue Architekturen für JavaScript-Frameworks? Getan hat sich seitdem nichts. Ein großer neuer Ansatz, der eine robuste und ausbaubare Grundlage schaffen will, ist nicht aufgekommen. Stattdessen werden Lösungen für Teilprobleme wie etwa asynchrones Laden von Scripten, Modularisierung und Polyfills für HTML5-APIs erarbeitet – damit will ich nicht infrage stellen, dass diese Arbeit wichtig ist und die JavaScript-Programmierung voranbringt.
Im Gegensatz zu Prototype und Mootools fassen jQuery, Underscore und CoffeeScript die Kern-Prototypen nicht an. Das hat seine Gründe, insbesondere der Verzicht auf die Erweiterung von DOM-Elementobjekten. Langfristig halte ich aber nichts davon, grundlegende Funktionalitäten in separaten Bibliotheken zu kapseln – und dadurch ständig zu wiederholen. Wir sollten langsam dazu kommen, für Grundfragen gemeinsame Antworten finden. Übergreifende Konventionen würden die JavaScript-Programmierung gleichzeitig vereinfachen und weiter professionalisieren.
Welche Ansätze sind zukunftsfähig und werden unabhängig von einzelnen Bibliotheken bleiben?
Der ECMAScript-Standard wird bleiben
ECMAScript 5 ist der aktuelle Standard hinter JavaScript. Er wurde vor mittlerweile eineinhalb Jahren verabschiedet und erfreut sich immer besserer Browserunterstützung.
Die neuen Object- und Array-Methoden, Property-Descriptoren und Zugriffsrechte und nicht zuletzt der Strict-Mode bieten eine hervorragende Grundlage für für JavaScript-Programmierung. Viele der Funktionen aus Underscore sind in ECMAScript 5 standardisiert und Underscore delegiert an diese vorhandene Methoden, sofern der Browser sie unterstützt.
Meine Forderung lautet daher: Die Standardbibliothek von ECMAScript 5 sollte die Grundlage für alle kommenden Scripte ein. Wir sollten in erster Linie den Standard nutzen und in älteren Browsern soweit es geht nachbauen – auch durch prototypische Erweiterung. Prototypische Erweiterung von Kernobjekten ist unproblematisch, solange man standardisierte Methoden gemäß dem Standard implementiert. Zukunftsfähige Bibliotheken sollten demnach nicht das Rad neu erfinden, sondern konsequent ES5-Syntax nutzen und sprachtypische APIs bereitstellen.
Es gibt bereits einige Polyfills für ECMAScript-5-Features:
- es5-shim: ECMAScript 5 compatibility shims for legacy JavaScript engines
- ddr-ecma5: Brings ECMAScript5 features to any browser
- es5: Parts of JavaScript/EcmaScript 5 which can be implemented in EcmaScript 3
Es spricht also nichts dagegen, bei neuen Projekten auf die praxistauglichen Features von ECMAScript 5 zu setzen, die sich in älteren Browsern nachrüsten lassen.
CoffeeScript wird – bis auf weiteres – bleiben und ECMAScript beeinflussen
CoffeeScript ist zurückhaltend und ermöglicht einem, schlichtes JavaScript mit einer angenehmeren Syntax zu schreiben. CoffeeScript-Pseudoklassen sind genial einfach im Vergleich zu denen in Prototype, Mootools und anderen Klassenbibliotheken. Der JavaScript-Erfinder Brendan Eich macht sich im ECMAScript-Komitee dafür stark, dass Ideen aus CoffeeScript in den kommenden ECMAScript-Standard zurückfließen werden – siehe auch diesen gemeinsamen Vortrag von Brendan Eich und Jeremy Ashkenas, dem Maintainer von CoffeeScript, Underscore und Backbone.
Es sollte dabei nicht vergessen werden, dass CoffeeScript nicht die Antwort auf sämtliche (Verständnis-)Probleme mit JavaScript ist. Die Sprache erbt viele Vor- und Nachteile von JavaScript und führt selbst einige Vor- und Nachteile ein, beispielsweise aufgrund der kompakten, aber mehrdeutigen Syntax.
Sicher gibt es auch Dopplungen zwischen reinem ECMAScript und CoffeeScript-Syntaxzucker. Beim Durchlaufen eines Arrays etwa hat man die Wahl zwischen der ECMAScript-Standardlösung mit einer Iteratorfunktion oder der CoffeeScript-Lösung. Letztere arbeitet mit einer herkömmlichen for-Schleife und ist daher performanter. Es liegt hier weiterhin in der Entscheidung des Programmierers. Die Wahl sollte möglichst einheitlich nach klaren Regeln erfolgen.