Anzeige
Anzeige
How-To
Artikel merken

Javascript mit Struktur: Patterns für performante Anwendungen

Javascript ist längst mehr als eine lose Sammlung von Funktionen, sondern eine der dynamischsten Programmiersprachen, die es derzeit gibt. Doch wie bringen Entwickler Struktur in ihre Anwendungen und machen sie performant und modular?

Von Sebastian Springer
6 Min. Lesezeit
Anzeige
Anzeige
(Bild: Shutterstock / REDPIXEL.PL)

Javascript verändert sich rasant: Für kaum eine andere Programmiersprache entstehen so schnell so viele Frameworks und Bibliotheken. Die Kehrseite ist jedoch, dass sie genauso schnell wieder verschwinden wie sie gekommen sind. Doch nicht nur das Javascript-Ökosystem verändert sich ständig, auch der Sprachkern entwickelt sich kontinuierlich weiter – und damit die Art der modernen Entwicklung von Web-Applikationen.

Anzeige
Anzeige

Die gute Nachricht für Entwickler ist, dass die neuen Funktionen die Arbeit mit Javascript erheblich erleichtern und potenziell weniger Quellcode notwendig ist. Außerdem sind die Strukturen der Applikationen klarer und besser lesbar und die Ausführung durch die Implementierung der Funktionen im Browser etwas performanter.

Javascript selbst erzwingt keine Konventionen. Die Sprache lädt eher dazu ein, recht lose strukturiert zu entwickeln. Um die Möglichkeiten von Javascript voll auszuschöpfen, lohnt es sich jedoch, den Quellcode immer gleich zu strukturieren. Das bedeutet, dass Entwickler entsprechende Konventionen einführen, wie bereits einige Best-Practice-Beispiele vorführen.

Anzeige
Anzeige

Klassen und Prototypen

Javascript verfolgt einen prototypenbasierten Ansatz, im Gegensatz zu klassenbasierten Sprachen wie Java. Die Dynamik, die dadurch entsteht, bezahlen Programmierer an anderer Stelle, beispielsweise durch eine umständliche Umsetzung von Vererbungen. Der ECMAScript-2015-Standard lässt erstmals Klassen in Javascript zu, sodass man in allen gängigen Browsern auf das class-Schlüsselwort zurückgreifen kann. Dass diese Klassen nach wie vor auf einem Prototypen-Konstrukt basieren, zeigt sich zum Beispiel daran, dass sich Eigenschaften nicht direkt in der Klasse definieren lassen, sondern nur in Objekten über den Konstruktor.

Anzeige
Anzeige

Javascript-Entwickler sind das schon gewohnt: Eigenschaften definieren sie im Konstruktor, Methoden im Prototyp. Mit der neuen Klassensyntax gibt es aber auch eine verkürzte Schreibweise, bei der Entwickler das function-Schlüsselwort einfach weglassen und statische sowie getter- und setter-Methoden für Eigenschaften definieren können.

Eine der wichtigsten Funktionen verbirgt sich hinter dem extends-Schlüsselwort. Damit lassen sich sehr einfach Vererbungen zwischen Klassen implementieren. Das untere Listing zeigt ein einfaches Beispiel einer Klasse in Javascript.

Anzeige
Anzeige

Bisher benutzte Features bleiben erhalten, sodass sich Prototypen einer Klasse zur Laufzeit verändern lassen. Programmierer können neue Klassen von einer bestehenden Konstruktor-Funktion ableiten, diese erben dann alle Methoden des Prototyps.

Der Einsatz von Klassen kann zudem die Performance verbessern: Viele Javascript-Engines optimieren den Programmablauf für Objekte des gleichen Typs. So verfügt die V8-Engine von Google über eine Funktion, die sich „Hidden Classes“ nennt und einen schnelleren Zugriff auf die Eigenschaften eines Objekts erlaubt. Für Objekte des gleichen Typs, also mit gemeinsamem Konstruktor, kommen die gleichen Hidden Classes zur Anwendung, was die Performance verbessert. Mit anderen Worten: Es spricht sehr viel für den Einsatz von Klassen in Javascript.

Listing: Klassen in Javascript


class User {
constructor(name, age) {
this.name = name;
this.age = age;
}

toString() {
return `${this.name} (${this.age})`;
}
}

Module

Doch nutzen Javascript-Klassen praktisch nichts, wenn Entwickler sie in einer großen Datei speichern. Die Applikation ist und bleibt dann unübersichtlich. Javascript-Module lösen dieses Problem. Mit ihnen können Entwickler die Funktionen ihrer Anwendungen in in sich geschlossenen Bausteinen ordnen, die mit den übrigen Teilen der Applikation nur lose gekoppelt sind.

Anzeige
Anzeige

Ein solches Modul liegt in einer eigenen Datei im Dateisystem. Das Modulsystem der Applikation lädt dieses nur, wenn es tatsächlich nötig ist. Abhängigkeiten zu anderen Modulen lösen sich automatisch auf. Eine so aufgebaute Applikation ist übersichtlich, lässt sich leichter warten und ist flexibler, weil Entwickler die einzelnen Module bei Bedarf einfach austauschen können.

Leider gibt es bisher für Javascript kein einheitliches Modulsystem. Zwei Standards haben sich zwar herausgebildet: Die Dojo-Foundation führte im Rahmen von require.js zum einen das Modulsystem AMD ein. Zum anderen gibt es die Modulsystem-Syntax CommonJS von Node.js. Auch standardisierte das ECMAScript 2015 neben den Klassen auch das Modulsystem. Doch leider griff das Standardisierungsgremium keinen dieser etablierten Standards auf, sondern definierte kurzerhand einen dritten. Dieser arbeitet mit den Schlüsselwörtern „import“ und „export“. Wie ein solches Modul in der Praxis aussieht, zeigt das folgende Listing.


// account.js
export default class Account {
constructor(username, password) {
this.username = username;
this.password = password;
}

authenticate() {
...
}

changePassword(newPass) {
...
}
}

// index.js
import Account from './account';
var acc = new Account('John', 'Password1');
acc.authenticate()
.then(() => acc.changePassword('topSecret!'));

Die separate Komponente „Loader“ lädt die Dateien und löst Abhängigkeiten auf. Der Standard ist mittlerweile zwar fixiert, doch kein Browser unterstützt ihn derzeit schon nativ. Mit Bibliotheken wie SystemJS kommen Programmierer trotzdem in den Genuss dieser Funktion. Daneben unterstützen diese Bibliotheken beim Produktivbetrieb: Statt den Benutzern hunderte einzelner Dateien auszuliefern, stellen Entwickler ihre Applikation damit in wenigen großen und optimierten Dateien bereit.

Anzeige
Anzeige

Asynchrone Programmierung

Wer seinen Nutzern einen Mehrwert bieten will, sollte die einzelnen Objekte und Module einer klassenbasierten, modularisierten Applikation untereinander kommunizieren lassen. Warum? Nun, im Normalfall läuft die Kommunikation über Methodenaufrufe. Doch spätestens bei asynchronen Operationen stößt dies an Grenzen. Denn bei Asynchronität steht der Rückgabewert einer Methode zum Zeitpunkt seiner Ausführung noch gar nicht fest.

Das bedeutet, das Programm läuft weiter ab – das Ergebnis der Operation, zum Beispiel ein Server-Call, liegt aber erst zu einem späteren Zeitpunkt vor. Damit die allgegenwärtige Asynchronität in Javascript kein Problem wird und Entwickler bei vielfach ineinandergeschachtelten Callback-Funktionen nicht den Überblick verlieren, greifen sie am besten auf die Funktion „Promises“ zurück.

Damit können Programmierer ein Objekt erstellen, das für eine asynchrone Operation steht. Sie arbeiten im Verlauf der Applikation mit dem Objekt und können beliebig viele Callback-Funktionen an den Erfolg oder Misserfolg der Operation knüpfen. Promises lassen sich außerdem verketten, sodass sie lineare Abläufe asynchroner Operationen bilden, bei denen jedes Kettenglied auf die Ausführung des vorherigen wartet. Reicht das nicht für die Flusssteuerung in einer Applikation, kann man Promises parallel laufen lassen und mit der weiteren Programmausführung erst dann fortfahren, wenn auch die letzte asynchrone Operation erfolgreich beendet ist.

Anzeige
Anzeige

Events

Ganz ohne Callback-Funktionen geht es in Javascript leider nicht und der Funktionsumfang von Promises hat Grenzen: Das Feature eignet sich hervorragend für asynchrone Operationen, bei denen es entweder Erfolg und Fehlschlag gibt. Für Abläufe mit mehreren Zuständen empfiehlt sich aber nicht Promises, sondern ein Architekturansatz, der so alt ist wie Javascript selbst: Die „Events“.

Dabei handelt es sich um nichts anderes als eine konkrete Implementierung des Observer-Patterns. Events registrieren Callback-Funktionen auf einem Objekt und laufen ab, wenn ein bestimmtes Ereignis eintritt. In diesem Fall sind die Callback-Funktionen die „Observer“ und das Objekt das „Observable“. In der täglichen Arbeit begegnet Entwicklern dieses Muster mit Sicherheit immer wieder. Beispielsweise ist jedes Element im DOM des Browsers ein Observable.

Gleiches gilt für den Webserver in Node.js, dessen Events Programmierer registrieren können. So haben sie die Möglichkeit, nicht nur die bestehenden Events bestimmter Objekte nutzen, sondern auch das Event-System in den Klassen und Objekten ihrer Applikation. Sie müssen nur eine Schnittstelle schaffen, über die sie die Callback-Funktionen registrieren. Typischerweise trägt eine solche Methode den Namen „on“. Außerdem benötigen die Entwickler eine Methode, über die sie die Events auslösen – häufig „emit“ genannt. Das nachfolgende Listing enthält den Quellcode für ein sehr einfaches Event-System mit Node.js.

Listing: Events mit Node.js


const EventEmitter = require('events');

class MessageBus extends EventEmitter {}

const msgBus = new MessageBus();

msgBus.on('someEvent', (data) => {
console.log(data);
});

msgBus.emit('someEvent', 'Hello World');


Entwickler sollten sich informieren, ob es für ihre Umgebung schon eine Implementierung gibt, bevor sie ihr eigenes Event-System realisieren. Im Browser können sie beispielsweise auf das jQuery-Event-System zugreifen oder serverseitig den Node.js-EventEmitter nutzen. Diese Implementierungen decken Standardfälle und Edge-Cases ab, deren Lösung sonst nur unnötig Zeit und Arbeit kosten.

Streams

Eine konkrete Implementierung eines Event-Systems sind Datenströme, auch Streams genannt. Diese gibt es sehr häufig in Node.js und mittlerweile auch verstärkt im Browser-Umfeld. Die Idee dahinter ist, dass sich der Informationsfluss in einer Applikation auch im Quellcode darstellen lässt. Der Entwickler kann diesen Datenstrom modifizieren, filtern, teilen oder auch zusammenführen. Mit diesem Muster kann er also den Datenfluss einer Web-Applikation vom Client zum Server und zurück sehr gut modellieren. Das etablierte Konzept von Datenströmen ist in Form von Unix-Pipes auch auf Betriebssystemebene anzutreffen.

Auch viele andere Sprachen kennen kontinuierliche Datenströme, die stets asynchron sind. Um sie zu implementieren, greifen Entwickler auf eine Event-Architektur zurück. Sie wissen in diesem Fall nicht, wann und wie viele Ereignisse in diesem Stream auftreten. Eines der vielen Umsetzungen dieses Patterns ist eben das stream-Modul von Node.js, auf dem auch das Build-System Gulp aufbaut. Dieses Werkzeug erledigt regelmäßig wiederkehrende Aufgaben im Build-Prozess einer Applikation wie das Minifizieren oder die Analyse des Quellcodes in Form von Streams.

Fazit

Javascript-Applikationen sind längst mehr als nur eine lose Sammlung von Funktionen, die Programmierer an die Events ihrer Formularelementen binden. Mittlerweile gibt es zahlreiche Erweiterungen des Sprachstandards sowie Bibliotheken und Frameworks, die Entwicklern dabei helfen, ihre Applikation zu strukturieren und performante, lesbare und wiederverwendbare Module zu erhalten, die in der Summe leistungsfähige Applikationen ergeben.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Ein Kommentar
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Thomas

Vielen Dank für den (eher) generellen Überblick. Mehr von solchen Artikeln!

Antworten

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige