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
- Augment.js: Enables use of modern JavaScript by augmenting built in objects
- 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.
Martin Grandrath hat kommentiert:
2011-07-02 17:55
Du sprichst mir aus der Seele. Ich schlage mich ebenfalls immer häufiger damit herum, dass Konzepte und Best Practices für große JavaScript-Projekte Mangelware sind. (In diesem Zusammenhang kann ich "JavaScript Patterns" von Stoyan Stefanov empfehlen, was aber leider auch nur ein Anfang ist.)
Das Trio jQuery + Underscore + Backbone habe ich mir ebenfalls angesehen und komme zu einem ähnlich Fazit wie du. Was mich besonders stört ist die Redundanz von Funktionalität zwischen jQuery und Underscore (extend, each, bind/proxy, ...). Meines Erachtens wird es allerhöchste Zeit, dass jQuery modular wird, da es sich mittlerweile wie ein unhandlicher Klotz anfühlt.
Viel wichtiger noch als die Frage, wie man grundlegende Kontrollstrukturen und Vererbungshierarchien implementiert, finde ich allerdings die Etablierung eines einheitlichen Systems zur Definition von Modulen sowie deren Abhängigkeiten. RequireJS macht hier einen hervorragenden Eindruck, da es die Verwendung von CommonJS AMD-Modulen erlaubt. Eine weitere Alternative, die ich mir derzeit näher ansehe, sind die Closure-Tools von Google. Diese liefern einiges mehr an "Struktur", sind aber im wesentlichen eine alles-oder-nichts-Veranstaltung (also nichts für Fans von Micro-Frameworks).
Noch eine Ergänzung zum Thema "Polyfills": Traceur[1] würde gut in diese Liste passen.
[1] http://code.google.com/p/traceur-compiler/
Oliver hat kommentiert:
2011-07-02 18:04
Mich persönlich stört mittlerweile viel mehr die Größe von jQuery, zumal man wie erwähnt nur einen Bruchteil der enthaltenen Funktionen nutzt. Vor allem im Bereich der mobilen Geräte ist das schon ein Problem, weil jQuery zu groß ist, um vom iphone gecached zu werden. Werden jetzt jedes Mal 31 kb übertragen, z. B. über gprs, dann kann das schon sehr nerven.
Ich habe aber leider noch keine Möglichkeit gefunden, so was komplexes wie jQuery sinnvoll zu entschlacken. :-(
Martin Grandrath hat kommentiert:
2011-07-03 21:48
@Oliver: Wenn du gezielt iPhone und Konsorten ansprichst, ist vielleicht zepto für dich interessant. Quasi jQuery ohne die ganzen IE-Extrawürste. ;-)
[1] http://zeptojs.com/
Oliver hat kommentiert:
2011-07-04 13:20
@Martin
Danke für den Hinweis. Aber allein für iphone alles neu zu schreiben ist ergonomisch nicht sinnvoll Ausserdem wenn ich einen berechtigten Verbesserungsvorschlag mache, und dieser mit einem RTFM geschlossen wird, obwohl genau die Fragen nicht erklärt sind, zeigt, dass es zepto wohl auch auf Dauer unbrauchbar bleiben wird. jQuery kompatibel zu tun und jQuery kompatibel zu sein ist halt doch ein Unterschied.
Felix Niklas hat kommentiert:
2011-07-04 16:06
Bin verblüfft darüber einen Artikel über solch ein 'cutting-edge' in meiner Muttersprache lesen. Liest man sonst immer nur auf English. Schöne Beispiele!
Dr. Azrael Tod hat kommentiert:
2011-07-04 17:26
Und schlimmer noch als dass das alles ein grauenvolles wirrwar aus verschiedenen libs und notationen ist, ist für mich der fakt dass ein übler aussieht als die andere. :-/
Aber ich werde wohl nie ein JS-Fan werden, das war schon vor Jahren ekelhaft und wird imho immer schlimmer.
Kahlil Lechelt hat kommentiert:
2011-08-07 13:54
Danke für den interessanten Artikel.
Deine Empfehlung für gegenwärtige Projekte wäre also z.B.:
jQuery mit Backbone und Underscore zu benutzen aber Polyfills zu benutzen und im ES5 "strict mode" zu programmieren?
Denkt ihr in dem Unternehmen in dem Du arbeitest auch über weitere Konventionen für umfangreiche JS-Apps nach?
molily hat kommentiert:
2011-08-09 23:32
Kahlil: Beim gegenwärtigen Projekt haben wir Backbone mehr der Einfachheit halber und aufgrund der vorhandenen Erfahrung damit gewählt. Das war eine schlechte Entscheidung, da das Projekt nicht einfach ist.
Bei einem neuen Projekte würde ich eher mit Dojo oder YUI anfangen, natürlich mit ES5-Shims. (Im Strict Mode entwickeln wir jetzt schon.) Backbone hat einfach zu viele Schwächen und vieles ist undurchdacht, unvollständig bzw. dem Entwickler überlassen. Es ist bitter nötig, aber sehr mühsam, unter diesen Voraussetzungen eigene Konventionen zu etablieren.
In einem kommenden Blogpost will ich beschreiben, mit welchen Problemen wir in diesem speziellen Fall kämpfen mussten und zu welchen Lösungen wir gekommen sind. Der Prozess ist natürlich noch nicht abgeschlossen, daher ist obige Blogpost eines von vielen Zwischenfazits.
Robert hat kommentiert:
2011-08-10 14:39
Mit Underscore und Backbone habe ich zwar bisher noch nicht gearbeitet, aber das Problem, dass JQuery zu viele ungenutzte Sachen enthält hatte ich auch schon öfter.
Ein interessanter Ansatz ist Ender: (http://www.dustindiaz.com/ender). Bisher habe ich aus Zeitmangel noch nicht damit gearbeitet, aber das wird sich definitiv noch ändern. Was haltet ihr davon?
Kahlil Lechelt hat kommentiert:
2011-08-13 18:46
Molily: Aha, ok das ist wirklich sehr interessant. Freue mich auf Deine nächsten Artikel!