Modulares JavaScript schreiben mit AMD, CommonJS & ES Harmony

Tweet

Modularität Die Bedeutung der Entkopplung Ihrer Anwendung

Wenn wir sagen, dass eine Anwendung modular ist, meinen wir im Allgemeinen, dass sie aus einer Reihe von hochgradig entkoppelten, unterschiedlichen Teilen der Funktionalität besteht, die in Modulen gespeichert sind. Wie Sie wahrscheinlich wissen, erleichtert die lose Kopplung die Wartbarkeit von Anwendungen, indem Abhängigkeiten nach Möglichkeit beseitigt werden. Wenn dies effizient umgesetzt wird, ist es recht einfach zu erkennen, wie sich Änderungen an einem Teil eines Systems auf einen anderen auswirken können.

Im Gegensatz zu einigen traditionelleren Programmiersprachen bietet die aktuelle Version von JavaScript (ECMA-262) Entwicklern jedoch keine Möglichkeit, solche Codemodule in einer sauberen, organisierten Weise zu importieren. Das ist eines der Probleme mit Spezifikationen, über die man sich erst in den letzten Jahren Gedanken gemacht hat, als der Bedarf an besser organisierten JavaScript-Anwendungen deutlich wurde.

Stattdessen müssen die Entwickler derzeit auf Variationen der Modul- oder Objektliteralmuster zurückgreifen. Bei vielen von ihnen werden Modulskripte im DOM aneinandergereiht, wobei Namespaces durch ein einziges globales Objekt beschrieben werden, bei dem es immer noch zu Namenskollisionen in der Architektur kommen kann. Es gibt auch keine saubere Möglichkeit, Abhängigkeiten ohne manuellen Aufwand oder Tools von Drittanbietern zu verwalten.

Während native Lösungen für diese Probleme in ES Harmony kommen werden, ist die gute Nachricht, dass das Schreiben von modularem JavaScript noch nie so einfach war und Sie heute damit beginnen können.

In diesem Artikel werden wir uns drei Formate für das Schreiben von modularem JavaScript ansehen: AMD, CommonJS und Vorschläge für die nächste Version von JavaScript, Harmony.

Vorspann Eine Anmerkung zu Skriptladern

Es ist schwierig, AMD und CommonJS-Module zu diskutieren, ohne über den Elefanten im Raum zu sprechen – Skriptlader. Gegenwärtig ist das Laden von Skripten ein Mittel zum Zweck, nämlich modulares JavaScript, das in heutigen Anwendungen verwendet werden kann – dafür ist die Verwendung eines kompatiblen Skriptladers leider notwendig. Um das meiste aus diesem Artikel herauszuholen, empfehle ich, ein grundlegendes Verständnis dafür zu erlangen, wie gängige Skriptlade-Tools funktionieren, damit die Erklärungen zu den Modulformaten im Kontext Sinn machen.

Es gibt eine Reihe großartiger Lader für die Handhabung des Modulladens in den Formaten AMD und CJS, aber meine persönlichen Vorlieben sind RequireJS und curl.js. Vollständige Tutorials zu diesen Tools liegen außerhalb des Rahmens dieses Artikels, aber ich kann empfehlen, John Hanns Beitrag über curl.js und James Burkes RequireJS-API-Dokumentation zu lesen, um mehr zu erfahren.

Aus einer Produktionsperspektive wird die Verwendung von Optimierungstools (wie dem RequireJS-Optimierer) zur Verkettung von Skripten für die Bereitstellung empfohlen, wenn mit solchen Modulen gearbeitet wird. Interessanterweise muss RequireJS mit dem AMD-Shim von Almond nicht in die bereitgestellte Site integriert werden, und was man als Skript-Loader bezeichnen könnte, kann leicht aus der Entwicklung ausgelagert werden.

Abgesehen davon würde James Burke wahrscheinlich sagen, dass die Möglichkeit, Skripte nach dem Laden der Seite dynamisch zu laden, immer noch ihre Anwendungsfälle hat, und RequireJS kann auch dabei helfen. Mit diesen Anmerkungen im Hinterkopf, lassen Sie uns beginnen.

AMD Ein Format für das Schreiben von modularem JavaScript im Browser

Das übergeordnete Ziel für das AMD-Format (Asynchronous Module Definition) ist es, eine Lösung für modulares JavaScript zu bieten, die Entwickler heute verwenden können. Es entstand aus den Erfahrungen von Dojo mit XHR+eval und die Befürworter dieses Formats wollten vermeiden, dass zukünftige Lösungen unter den Schwächen der bisherigen leiden.

Das AMD-Modulformat selbst ist ein Vorschlag für die Definition von Modulen, bei denen sowohl das Modul als auch die Abhängigkeiten asynchron geladen werden können. Es hat eine Reihe von eindeutigen Vorteilen, einschließlich der Tatsache, dass es sowohl asynchron als auch hochflexibel ist, was die enge Kopplung aufhebt, die man normalerweise zwischen Code und Modulidentität finden kann. Viele Entwickler verwenden es gerne und man könnte es als zuverlässiges Sprungbrett in Richtung des für ES Harmony vorgeschlagenen Modulsystems betrachten.

AMD begann als Entwurfsspezifikation für ein Modulformat auf der CommonJS-Liste, aber da es nicht in der Lage war, einen vollständigen Konsens zu erreichen, wurde die weitere Entwicklung des Formats in die amdjs-Gruppe verlagert.

Heute wird es von Projekten wie Dojo (1.7), MooTools (2.0), Firebug (1.8) und sogar jQuery (1.7) unterstützt. Obwohl der Begriff CommonJS AMD Format gelegentlich in freier Wildbahn gesehen wurde, ist es am besten, sich nur auf AMD oder Async Module Support zu beziehen, da nicht alle Teilnehmer der CJS-Liste dies verfolgen wollten.

Anmerkung: Es gab eine Zeit, in der der Vorschlag als Modules Transport/C bezeichnet wurde, aber da die Spezifikation nicht für den Transport bestehender CJS-Module gedacht war, sondern eher für die Definition von Modulen, machte es mehr Sinn, sich für die AMD-Namenskonvention zu entscheiden.

Einstieg in Module

Die beiden Schlüsselkonzepte, die man hier kennen muss, sind die Idee einer define Methode zur Erleichterung der Moduldefinition und einer require Methode zur Handhabung des Ladens von Abhängigkeiten. define wird verwendet, um benannte oder unbenannte Module auf der Grundlage des Vorschlags mit der folgenden Signatur zu definieren:

Wie Sie an den Inline-Kommentaren erkennen können, ist das module_id ein optionales Argument, das typischerweise nur erforderlich ist, wenn Nicht-AMD-Verkettungstools verwendet werden (es kann einige andere Randfälle geben, in denen es ebenfalls nützlich ist). Wenn dieses Argument weggelassen wird, nennen wir das Modul anonymous.

Wenn man mit anonymen Modulen arbeitet, ist die Idee der Identität eines Moduls DRY, was es trivial macht, die Duplizierung von Dateinamen und Code zu vermeiden. Da der Code portabler ist, kann er leicht an andere Stellen (oder innerhalb des Dateisystems) verschoben werden, ohne dass der Code selbst oder seine ID geändert werden muss. module_id ist gleichbedeutend mit Ordnerpfaden in einfachen Paketen und wenn sie nicht in Paketen verwendet werden. Entwickler können denselben Code auch in mehreren Umgebungen ausführen, indem sie einfach einen AMD-Optimierer verwenden, der mit einer CommonJS-Umgebung wie r.js arbeitet.

Zurück zur define-Signatur, das dependencies-Argument stellt ein Array von Abhängigkeiten dar, die für das zu definierende Modul erforderlich sind, und das dritte Argument (‚definition function‘) ist eine Funktion, die ausgeführt wird, um Ihr Modul zu instanziieren. Ein Barebone-Modul könnte wie folgt definiert werden:

Verständnis von AMD: define()

require hingegen wird typischerweise zum Laden von Code in einer JavaScript-Datei der obersten Ebene oder innerhalb eines Moduls verwendet, wenn Sie dynamisch Abhängigkeiten abrufen möchten. Ein Beispiel für seine Verwendung ist:

Verstehen von AMD: require()

Dynamisch geladene Abhängigkeiten

Verstehen von AMD: plugins

Nachfolgend ein Beispiel für die Definition eines AMD-kompatiblen Plugins:

Hinweis: Obwohl css! für das Laden von CSS-Abhängigkeiten im obigen Beispiel enthalten ist, ist es wichtig, sich daran zu erinnern, dass dieser Ansatz einige Nachteile hat, wie zum Beispiel, dass es nicht möglich ist, festzustellen, wann das CSS vollständig geladen ist. Abhängig davon, wie Sie Ihren Build angehen, kann es auch dazu führen, dass CSS als eine Abhängigkeit in der optimierten Datei enthalten ist, also verwenden Sie CSS als eine geladene Abhängigkeit in solchen Fällen mit Vorsicht.

Laden von AMD-Modulen mit require.js

Laden von AMD-Modulen mit curl.js

Module mit aufgeschobenen Abhängigkeiten

Warum ist AMD eine bessere Wahl für das Schreiben von modularem JavaScript?

  • Bietet einen klaren Vorschlag, wie man die Definition flexibler Module angehen kann.
  • Erheblich sauberer als die derzeitigen Lösungen mit globalem Namespace und <script>-Tag, auf die sich viele von uns verlassen. Es gibt einen sauberen Weg, eigenständige Module und deren Abhängigkeiten zu deklarieren.
  • Moduldefinitionen sind gekapselt und helfen uns, die Verschmutzung des globalen Namespaces zu vermeiden.
  • Wirkt besser als einige alternative Lösungen (z.B. CommonJS, das wir uns in Kürze ansehen werden). Hat keine Probleme mit domänenübergreifenden, lokalen oder Debugging-Problemen und ist nicht auf serverseitige Tools angewiesen, die verwendet werden müssen. Die meisten AMD-Loader unterstützen das Laden von Modulen im Browser ohne einen Build-Prozess.
  • Bietet einen „Transport“-Ansatz für die Aufnahme mehrerer Module in eine einzige Datei. Andere Ansätze wie CommonJS müssen sich noch auf ein Transportformat einigen.
  • Es ist möglich, Skripte „lazy“ zu laden, wenn dies benötigt wird.

AMD-Module mit Dojo

Die Definition von AMD-kompatiblen Modulen mit Dojo ist ziemlich einfach. Wie oben definiert man alle Modulabhängigkeiten in einem Array als erstes Argument und gibt einen Callback (Factory) an, der das Modul ausführt, sobald die Abhängigkeiten geladen wurden. z. B.g:

define(, function( Tooltip ){ //Our dijit tooltip is now available for local use new Tooltip(...);});

Beachten Sie die anonyme Natur des Moduls, das nun sowohl von einem Dojo asynchronen Lader, RequireJS oder dem Standard dojo.require() Modul Lader, den Sie vielleicht gewohnt sind zu benutzen, konsumiert werden kann.

Für diejenigen, die sich über die Modulreferenzierung wundern, gibt es einige interessante Probleme, die hier nützlich zu wissen sind. Obwohl der von AMD befürwortete Weg, Module zu referenzieren, sie in der Abhängigkeitsliste mit einer Reihe von passenden Argumenten deklariert, wird dies nicht vom Dojo 1.6 Build System unterstützt – es funktioniert wirklich nur für AMD-kompatible Lader. e.g:

define(, function( cookie, Tooltip ){ var cookieValue = cookie("cookieName"); new Tree(...); });

Dies hat viele Vorteile gegenüber verschachteltem Namespacing, da Module nicht mehr jedes Mal direkt komplette Namespaces referenzieren müssen – alles was wir benötigen ist der ‚dojo/cookie‘ Pfad in den Abhängigkeiten, der, sobald er einem Argument zugeordnet ist, durch diese Variable referenziert werden kann. Dies beseitigt die Notwendigkeit, wiederholt ‚dojo.‘ in Ihren Anwendungen einzugeben.

Anmerkung: Obwohl Dojo 1.6 offiziell keine benutzerbasierten AMD-Module (noch asynchrones Laden) unterstützt, ist es möglich, dies mit Dojo unter Verwendung einer Reihe von verschiedenen Skriptladern zum Laufen zu bringen. Derzeit wurden alle Dojo Kern- und Dijit-Module in die AMD-Syntax umgewandelt und eine verbesserte allgemeine AMD-Unterstützung wird wahrscheinlich zwischen 1.7 und 2.0 kommen.

Das letzte Problem, dessen man sich bewusst sein sollte, ist, dass, wenn man weiterhin das Dojo-Build-System verwenden möchte oder ältere Module auf diesen neueren AMD-Stil migrieren möchte, die folgende ausführlichere Version eine einfachere Migration ermöglicht. Beachten Sie, dass dojo und dijit ebenfalls als Abhängigkeiten referenziert werden:

AMD Module Design Patterns (Dojo)

Wenn Sie einen meiner früheren Beiträge über die Vorteile von Design Patterns verfolgt haben, wissen Sie, dass diese sehr effektiv sein können, um die Strukturierung von Lösungen für allgemeine Entwicklungsprobleme zu verbessern. John Hann hat vor kurzem einen ausgezeichneten Vortrag über AMD-Modulentwurfsmuster gehalten, in dem er Singleton, Decorator, Mediator und andere behandelt hat. Ich empfehle dringend, sich seine Folien anzusehen, wenn Sie die Gelegenheit dazu haben.

Einige Beispiele für diese Muster sind unten zu finden:

Dekorator-Muster:

Adapter-Muster

AMD-Module mit jQuery

Die Grundlagen

Im Gegensatz zu Dojo wird jQuery wirklich nur mit einer Datei geliefert, aber angesichts der Plugin-basierten Natur der Bibliothek können wir im Folgenden demonstrieren, wie einfach es ist, ein AMD-Modul zu definieren, das es verwendet.

Es gibt jedoch etwas, das in diesem Beispiel fehlt, und das ist das Konzept der Registrierung.

Registrierung von jQuery als asynchrones Modul

Eine der wichtigsten Funktionen, die in jQuery 1.7 Einzug gehalten haben, war die Unterstützung für die Registrierung von jQuery als asynchrones Modul. Es gibt eine Reihe von kompatiblen Skriptladern (einschließlich RequireJS und curl), die in der Lage sind, Module in einem asynchronen Modulformat zu laden, was bedeutet, dass weniger Hacks erforderlich sind, um die Dinge zum Laufen zu bringen.

Aufgrund der Popularität von jQuery müssen AMD-Lader mehrere Versionen der Bibliothek berücksichtigen, die in dieselbe Seite geladen werden, da man idealerweise nicht möchte, dass mehrere verschiedene Versionen gleichzeitig geladen werden. Lader haben die Möglichkeit, dieses Problem entweder speziell zu berücksichtigen oder ihre Benutzer darauf hinzuweisen, dass es bekannte Probleme mit Skripten von Drittanbietern und deren Bibliotheken gibt.

Der Zusatz 1.7 hilft, Probleme mit anderem Code von Drittanbietern auf einer Seite zu vermeiden, die versehentlich eine Version von jQuery auf die Seite laden, die der Eigentümer nicht erwartet hat. Man möchte nicht, dass andere Instanzen die eigene überlagern und so kann dies von Vorteil sein.

Die Art und Weise, wie dies funktioniert, ist, dass der verwendete Skript-Loader anzeigt, dass er mehrere jQuery-Versionen unterstützt, indem er angibt, dass eine Eigenschaft define.amd.jQuery gleich true ist. Für diejenigen, die an spezifischeren Implementierungsdetails interessiert sind, registrieren wir jQuery als benanntes Modul, da die Gefahr besteht, dass es mit anderen Dateien verkettet werden kann, die AMDs define()-Methode verwenden, aber kein richtiges Verkettungsskript verwenden, das anonyme AMD-Moduldefinitionen versteht.

Die benannte AMD bietet die Sicherheit, sowohl robust als auch sicher für die meisten Anwendungsfälle zu sein.

Smarter jQuery Plugins

Ich habe hier kürzlich einige Ideen und Beispiele diskutiert, wie jQuery Plugins unter Verwendung von Universal Module Definition (UMD) Patterns geschrieben werden könnten. UMDs definieren Module, die sowohl auf dem Client als auch auf dem Server und mit allen gängigen Skript-Loadern funktionieren können. Obwohl es sich hier noch um einen neuen Bereich handelt, in dem viele Konzepte noch in der Entwicklung sind, können Sie sich die Codebeispiele im Abschnitt AMD && CommonJS ansehen und mir mitteilen, ob es etwas gibt, was wir besser machen können.

Welche Script Loader & Frameworks unterstützen AMD?

In-Browser:
Serverseitig:
  • RequireJS http://requirejs.org
  • PINF http://github.com/pinf/loader-js

AMD-Schlussfolgerungen

Die obigen Beispiele sind sehr trivial, um zu zeigen, wie nützlich AMD-Module wirklich sein können, aber sie bieten hoffentlich eine Grundlage für das Verständnis, wie sie funktionieren.

Es ist vielleicht interessant zu wissen, dass viele sichtbare große Anwendungen und Unternehmen derzeit AMD-Module als Teil ihrer Architektur verwenden. Dazu gehören IBM und der BBC iPlayer, was zeigt, wie ernsthaft dieses Format von Entwicklern auf Unternehmensebene in Betracht gezogen wird.

Weitere Gründe, warum sich viele Entwickler für die Verwendung von AMD-Modulen in ihren Anwendungen entscheiden, finden Sie in diesem Beitrag von James Burke.

CommonJS Ein für den Server optimiertes Modulformat

CommonJS ist eine freiwillige Arbeitsgruppe, deren Ziel es ist, JavaScript-APIs zu entwerfen, zu prototypisieren und zu standardisieren. Bislang haben sie versucht, Standards für Module und Pakete zu ratifizieren. Der CommonJS-Modulvorschlag spezifiziert eine einfache API für die Deklaration von Modulen auf der Serverseite und versucht im Gegensatz zu AMD, eine breitere Palette von Anliegen wie io, Dateisystem, Versprechen und mehr abzudecken.

Einstieg

Aus Sicht der Struktur ist ein CJS-Modul ein wiederverwendbares Stück JavaScript, das bestimmte Objekte exportiert, die jedem abhängigen Code zur Verfügung gestellt werden – es gibt normalerweise keine Funktions-Wrapper um solche Module herum (daher werden Sie hier zum Beispiel define nicht verwenden).

Auf einer hohen Ebene enthalten sie im Grunde zwei primäre Teile: eine freie Variable namens exports, die die Objekte enthält, die ein Modul anderen Modulen zur Verfügung stellen möchte, und eine require-Funktion, die Module verwenden können, um die Exporte anderer Module zu importieren.

Verstehen von CJS: require() und exports

Grundlegender Verbrauch von exports

AMD-Äquivalent des ersten CJS-Beispiels

Verbrauch mehrerer Abhängigkeiten

app.js
bar.js
exports.name = 'bar';
foo.js
require('./bar');exports.helloWorld = function(){ return 'Hello World!!''}

Welche Loader & Frameworks unterstützen CJS?

In-browser:
  • curl.js http://github.com/unscriptable/curl
  • SproutCore 1.1 http://sproutcore.com
  • PINF http://github.com/pinf/loader-js
  • (und mehr)
Server-seitig:

Ist CJS für den Browser geeignet?

Es gibt Entwickler, die der Meinung sind, dass CommonJS besser für die serverseitige Entwicklung geeignet ist, was ein Grund dafür ist, dass derzeit Uneinigkeit darüber herrscht, welches Format als De-facto-Standard im Vor-Harmony-Zeitalter verwendet werden soll und wird. Zu den Argumenten gegen CJS gehört auch der Hinweis, dass viele CommonJS-APIs serverorientierte Features adressieren, die man auf Browserebene in JavaScript einfach nicht implementieren könnte – beispielsweise könnten io, system und js aufgrund der Art ihrer Funktionalität als nicht implementierbar angesehen werden.

Abgesehen davon ist es nützlich zu wissen, wie man CJS-Module unabhängig davon strukturiert, damit wir besser einschätzen können, wie sie sich einfügen, wenn wir Module definieren, die überall verwendet werden können. Zu den Modulen, die sowohl auf dem Client als auch auf dem Server Anwendung finden, gehören Validierungs-, Konvertierungs- und Template-Engines. Einige Entwickler gehen bei der Wahl des Formats so vor, dass sie sich für CJS entscheiden, wenn ein Modul in einer serverseitigen Umgebung verwendet werden kann, und AMD verwenden, wenn dies nicht der Fall ist.

Da AMD-Module in der Lage sind, Plugins zu verwenden und granularere Dinge wie Konstruktoren und Funktionen zu definieren, macht dies Sinn. CJS-Module können nur Objekte definieren, was mühsam sein kann, wenn man versucht, Konstruktoren aus ihnen herauszuholen.

Auch wenn es den Rahmen dieses Artikels sprengt, haben Sie vielleicht bemerkt, dass bei der Diskussion von AMD und CJS verschiedene Arten von „require“-Methoden erwähnt wurden.

Die Sorge bei einer ähnlichen Namenskonvention ist natürlich die Verwirrung und die Gemeinschaft ist derzeit geteilter Meinung über die Vorzüge einer globalen require-Funktion. John Hanns Vorschlag ist, dass es sinnvoller sein könnte, die globale Lademethode in etwas anderes umzubenennen (z.B. den Namen der Bibliothek), anstatt sie „require“ zu nennen, was wahrscheinlich das Ziel verfehlen würde, die Benutzer über den Unterschied zwischen einem globalen und einem inneren require zu informieren. Aus diesem Grund verwendet ein Lader wie curl.js curl() anstelle von require.

AMD && CommonJS Konkurrierende, aber gleichwertige Standards

Während dieser Artikel mehr Wert auf die Verwendung von AMD gegenüber CJS gelegt hat, ist die Realität, dass beide Formate gültig sind und eine Verwendung haben.

AMD verfolgt bei der Entwicklung einen Browser-First-Ansatz und setzt auf asynchrones Verhalten und vereinfachte Rückwärtskompatibilität, hat aber kein Konzept für File I/O. Es unterstützt Objekte, Funktionen, Konstruktoren, Strings, JSON und viele andere Arten von Modulen, die nativ im Browser laufen. Es ist unglaublich flexibel.

CommonJS hingegen verfolgt einen Server-first-Ansatz, der synchrones Verhalten voraussetzt, kein globales Gepäck, wie John Hann es nennen würde, und versucht, für die Zukunft (auf dem Server) vorzusorgen. Wir meinen damit, dass CJS durch die Unterstützung von unverpackten Modulen den ES.next/Harmony-Spezifikationen ein wenig näher kommt und Sie von dem von AMD erzwungenen define()-Wrapper befreit. CJS-Module unterstützen jedoch nur Objekte als Module.

Obwohl die Idee eines weiteren Modulformats entmutigend sein mag, könnten Sie an einigen Beispielen der Arbeit an hybriden AMD/CJS- und Univeral AMD/CJS-Modulen interessiert sein.

Basic AMD Hybrid Format (John Hann)

AMD/CommonJS Universal Module Definition (Variation 2, UMDjs)

Extensible UMD Plugins With (Variation von mir und Thomas Davis).

core.js
myExtension.js
;(function ( name, definition ) { var theModule = definition(), hasDefine = typeof define === 'function', hasExports = typeof module !== 'undefined' && module.exports; if ( hasDefine ) { // AMD Module define(theModule); } else if ( hasExports ) { // Node.js Module module.exports = theModule; } else { // Assign to common namespaces or simply the global object (window) // account for for flat-file/global module extensions var obj = null; var namespaces = name.split("."); var scope = (this.jQuery || this.ender || this.$ || this); for (var i = 0; i 
app.js
$(function(){ // the plugin 'core' is exposed under a core namespace in // this example which we first cache var core = $.core; // use then use some of the built-in core functionality to // highlight all divs in the page yellow core.highlightAll(); // access the plugins (extensions) loaded into the 'plugin' // namespace of our core module: // Set the first div in the page to have a green background. core.plugin.setGreen("div:first"); // Here we're making use of the core's 'highlight' method // under the hood from a plugin loaded in after it // Set the last div to the 'errorColor' property defined in // our core module/plugin. If you review the code further down // you'll see how easy it is to consume properties and methods // between the core and other plugins core.plugin.setRed('div:last');});

ES Harmony Modules Of The Future

TC39, das Standardisierungsgremium, das mit der Definition der Syntax und Semantik von ECMAScript und seinen zukünftigen Iterationen beauftragt ist, besteht aus einer Reihe sehr intelligenter Entwickler. Einige dieser Entwickler (wie z.B. Alex Russell) haben die Entwicklung der JavaScript-Nutzung für umfangreiche Entwicklungen in den letzten Jahren genau verfolgt und sind sich der Notwendigkeit besserer Sprachfunktionen für das Schreiben von modularerem JS bewusst.

Aus diesem Grund gibt es derzeit Vorschläge für eine Reihe interessanter Erweiterungen der Sprache, darunter flexible Module, die sowohl auf dem Client als auch auf dem Server funktionieren können, ein Modul-Loader und mehr. In diesem Abschnitt zeige ich Ihnen einige Code-Beispiele der Syntax für Module in ES.next, damit Sie einen Vorgeschmack auf das bekommen, was kommen wird.

Hinweis: Obwohl sich Harmony noch in der Vorschlags-Phase befindet, können Sie bereits (Teil-)Funktionen von ES.next ausprobieren, die dank des Traceur-Compilers von Google die native Unterstützung für das Schreiben von modularem JavaScript ansprechen. Um Traceur in weniger als einer Minute zum Laufen zu bringen, lesen Sie diesen Leitfaden. Es gibt auch eine JSConf-Präsentation darüber, die sich lohnt, wenn Sie mehr über das Projekt erfahren möchten.

Module mit Importen und Exporten

Wenn Sie die Abschnitte über AMD- und CJS-Module gelesen haben, sind Sie vielleicht mit dem Konzept der Modulabhängigkeiten (Importe) und Modulexporte (oder die öffentlichen API/Variablen, die wir anderen Modulen erlauben, zu verwenden) vertraut. In ES.next wurden diese Konzepte in einer etwas prägnanteren Art und Weise vorgeschlagen, wobei Abhängigkeiten mit einem import-Schlüsselwort angegeben werden. export unterscheidet sich nicht wesentlich von dem, was wir vielleicht erwarten, und ich denke, viele Entwickler werden sich den folgenden Code ansehen und ihn sofort verstehen.

  • Import-Deklarationen binden die Exporte eines Moduls als lokale Variablen und können umbenannt werden, um Namenskollisionen/-konflikte zu vermeiden.
  • Export-Deklarationen erklären, dass eine lokale Bindung eines Moduls nach außen sichtbar ist, so dass andere Module die Exporte lesen, aber nicht verändern können. Interessanterweise können Module Kind-Module exportieren, aber keine Module, die anderswo definiert wurden. Sie können Exporte auch umbenennen, so dass sich ihr externer Name von ihrem lokalen Namen unterscheidet.

Module, die aus entfernten Quellen geladen werden

Die Modulvorschläge sind auch für Module geeignet, die aus der Ferne geladen werden (z.B. ein API-Wrapper eines Drittanbieters), was das Laden von Modulen aus externen Quellen vereinfacht. Hier ein Beispiel, wie wir das oben definierte Modul einbinden und verwenden:

Modullader-API

Der vorgeschlagene Modullader beschreibt eine dynamische API zum Laden von Modulen in stark kontrollierten Kontexten. Zu den vom Loader unterstützten Signaturen gehören load( url, moduleInstance, error) zum Laden von Modulen, createModule( object, globalModuleReferences) und andere. Hier ist ein weiteres Beispiel für das dynamische Laden des Moduls, das wir ursprünglich definiert haben. Beachten Sie, dass im Gegensatz zum letzten Beispiel, bei dem wir ein Modul aus einer entfernten Quelle bezogen haben, die API des Modulladers besser für dynamische Kontexte geeignet ist.

Loader.load('http://addyosmani.com/factory/cakes.js', function(cakeFactory){ cakeFactory.oven.makeCupcake('chocolate'); });

CommonJS-ähnliche Module für den Server

Für Entwickler, die serverorientiert arbeiten, ist das für ES.next vorgeschlagene Modulsystem nicht nur auf die Anzeige von Modulen im Browser beschränkt. Im Folgenden sehen Sie ein CJS-ähnliches Modul, das für den Einsatz auf dem Server vorgeschlagen wird:

// io/File.jsexport function open(path) { ... };export function close(hnd) { ... };
module lexer from 'compiler/LexicalHandler';module stdlib from '@std'; //... scan(cmdline) ...

Klassen mit Konstruktoren, Gettern & Settern

Der Begriff der Klasse war schon immer ein Streitpunkt mit Puristen, und wir haben uns bisher damit begnügt, entweder auf die prototypische Natur von JavaScript zurückzugreifen oder Frameworks oder Abstraktionen zu verwenden, die die Möglichkeit bieten, Klassendefinitionen in einer Form zu verwenden, die auf das gleiche prototypische Verhalten zurückgeht.

In Harmony sind Klassen ein Teil der Sprache, zusammen mit Konstruktoren und (endlich) einem gewissen Maß an echter Privatsphäre. In den folgenden Beispielen habe ich einige Inline-Kommentare eingefügt, um Ihnen zu zeigen, wie Klassen strukturiert sind, aber vielleicht fällt Ihnen auch auf, dass das Wort „Funktion“ hier fehlt. Das ist kein Tippfehler: TC39 hat sich bewusst bemüht, den Missbrauch des Schlüsselworts function für alles zu verringern, und wir hoffen, dass dies dazu beiträgt, das Schreiben von Code zu vereinfachen.

class Cake{ // We can define the body of a class' constructor // function by using the keyword 'constructor' followed // by an argument list of public and private declarations. constructor( name, toppings, price, cakeSize ){ public name = name; public cakeSize = cakeSize; public toppings = toppings; private price = price; } // As a part of ES.next's efforts to decrease the unnecessary // use of 'function' for everything, you'll notice that it's // dropped for cases such as the following. Here an identifier // followed by an argument list and a body defines a new method addTopping( topping ){ public(this).toppings.push(topping); } // Getters can be defined by declaring get before // an identifier/method name and a curly body. get allToppings(){ return public(this).toppings; } get qualifiesForDiscount(){ return private(this).price > 5; } // Similar to getters, setters can be defined by using // the 'set' keyword before an identifier set cakeSize( cSize ){ if( cSize 

ES Harmony Conclusions

As you can see, ES.next is coming with some exciting new additions. Although Traceur can be used to an extent to try our such features in the present, remember that it may not be the best idea to plan out your system to use Harmony (just yet). There are risks here such as specifications changing and a potential failure at the cross-browser level (IE9 for example will take a while to die) so your best bets until we have both spec finalization and coverage are AMD (for in-browser modules) and CJS (for those on the server).

Conclusions And Further Reading A Review

In this article we've reviewed several of the options available for writing modular JavaScript using modern module formats. These formats have a number of advantages over using the (classical) module pattern alone including: avoiding a need for developers to create global variables for each module they create, better support for static and dynamic dependency management, improved compatibility with script loaders, better (optional) compatibility for modules on the server and more.

In short, I recommend trying out what's been suggested today as these formats offer a lot of power and flexibility that can help when building applications based on many reusable blocks of functionality.

And that's it for now. If you have further questions about any of the topics covered today, feel free to hit me up on twitter and I'll do my best to help!

Copyright Addy Osmani, 2012. All Rights Reserved.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.