JavaScript/Tutorials/Namensraum

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Namensräume (im Englischen namespaces genannt) dienen beim Programmieren dazu, Programmteile, oder Module von Programmen voneinander zu trennen. Die verschiedenen Programmiersprachen lösen dieses Problem auf ihre jeweilige Art, in JavaScript jedoch ist syntaktisch keine Möglichkeit für Namensräume vorgesehen. Die Objektstruktur der Sprache macht es aber möglich, etwas ähnliches wie Namensräume zu haben.

Grundlage: Globale Variablen und lokale Variablen

Der Geltungsbereich von Variablen in JavaScript bietet die Möglichkeit, ein Objekt im globalen Gültigkeitsbereich zu definieren:

Beispiel
myObj = new Object();

const myObj2 = {};
Die Notation ohne das Schlüsselwort var macht aus der Variable myObj eine Eigenschaft des window-Objektes, nämlich window.myObj, was in JavaScript auch als "globale Variable" bezeichnet wird. In diesem Fall kann man auch das Schlüsselwort const benutzen, da das zugewiesene Objekt später nicht durch ein anderes ersetzt werden soll, auch wenn dieses Objekt später vielleicht verändert wird, was nach der Deklaration mit const möglich ist.

Das sieht zunächst sehr trivial aus, bietet aber interessante Möglichkeiten, da an ein solches Objekt beliebige Eigenschaften (lies: Variablen) und Methoden (lies: Funktionen) gebunden werden können. Hier ein Vergleich:

Beispiel
function init () { alert("start!"); }

myObj.init = function () { alert("myObj start!"); }

Man kann die Funktion init als globale Funktion betrachten. Ihre Definition geschieht im globalen "Namensraum" (lies: Geltungsbereich). Damit wird sie, und das ist im Browser so geregelt, eine Methode des window-Objekts und kann z. B. mittels window.init() aufgerufen werden. Anders verhält es sich mit myObj.init(). Ein solcher Aufruf würde eine völlig andere Funktion mit dem gleichen Namen init ausführen, nämlich die gleichnamige Methode des myObj-Objekts, aber eben nicht window.init. Und das ist ja der Sinn von Namensräumen.

Objekte als Namensräume

Möchte man ein Programm auf einer Seite einbinden, dessen Variablen und Funktionen technisch von möglichen anderen JavaScript-Programmen getrennt sein sollen, kann man ein Objekt als Namensraum benutzen und die Variablen und Funktionen zu dessen Eigenschaften und Methoden machen.

Viele in JavaScript geschriebene Programme, die man als Komponenten in seine eigenen Projekte einbinden kann, verwenden ihren eigenen Namensraum, indem sie ein eigenes Objekt als eine Eigenschaft des window-Objektes definieren. Dazu zählen Frameworks wie jQuery, oder Editoren wie Codemirror oder TinyMCE.

Alternative zu Namensräumen

In JavaScript kann es sinnvoll sein, sein Projekt unter einen Namensraum zu stellen, insbesondere dann, wenn man aus anderen Kontexten auf dessen Daten, also Eigenschaften, zurückgreifen möchte (z. B. um Einstellungen für die Funktionsweise dieses Programms vorzunehmen) oder um seine Funktionalitäten für andere Projekte verfügbar zu machen (das tut das oben erwähnte jQuery).

Namensräume sind aber kein Ersatz für eine saubere Modularisierung. Dieses Thema wird in einem eigenen Kapitel des Wikis behandelt.

Es gibt aber auch genügend Fälle, wo man keine Zugriffsmöglichkeit "von außen" braucht und der zu kapselnde Code den Aufwand für ein Modul nicht rechtfertigt. Es genügt die Kapselung der verwendeten Variablen. Es gibt zwei einfache Möglichkeiten, dies in JavaScript zu erreichen.

Kapselung mit einem Funktionsausdruck

Eine Funktion ist ein Objekt wie jedes andere auch. Funktionsausdrücke erzeugen ein namenloses Funktionsobjekt, das man in einer Variablen speichern kann, als Parameter übergeben kann oder auch einfach nur ausführen und vergessen kann. Letzteres nennt man im Englischen "immediately invoked function expression" (IIFE), zu deutsch "sofort ausgeführter Funktionsausdruck".

Zweck des IIFE ist es, einen Container für Variablen zu bieten, die nach Ende der Funktion wieder verschwinden. Die von der IIFE gebildete Closure kann auch dazu dienen, einen gekapselten Datenspeicher für Funktionen zu bilden, die innerhalb des IIFE erzeugt und beispielsweise als Eventhandler registriert werden.

Grundmuster für einen sofort ausgeführter Funktionsausdruck (IIFE)
(
   function (daten) {
      // beliebiger Code
   }
) (daten);

Kern des IIFE ist eine anonyme Funktion. Damit aus dem Funktionsausdruck kein Funktionsstatement wird, darf das Schlüsselwort function nicht zu Beginn des Statements stehen. Dieser Umstand ist der Historie der JavaScript Syntax geschuldet. Es gibt drei typische Möglichkeiten, diesen Zweck zu erreichen.

Das Beispiel oben zeigt den „Klassiker“ mit Klammern. Er hat den Vorteil, sich an jeder Stelle verwenden zu lassen, sogar inmitten eines anderen Ausdrucks, und darum verwenden ihn viele Programmierer konsistent an jeder Stelle so.

Durch (function() { }) entsteht ein Funktionsobjekt. Auf dieses Objekt wird der function call Operator () angewendet, d. h. die Funktion wird aufgerufen. Danach steht nur noch das Funktionsergebnis zur Verfügung, das Funktionsobjekt selbst ist nicht mehr zugänglich und wird dem Garbage Collector überantwortet.

Wenn kein Funktionsergebnis gebraucht wird, gibt es drei alternative Schreibweisen. Die beiden ersten nutzen den Umstand, dass + und - als unäre Operatoren für Zahlen definiert sind, aber auch jedes andere Objekt akzeptieren. Der Funktionsaufruf Operator hat eine höhrere Priorität als + oder -, wird also zuerst ausgeführt. Die dritte Schreibweise ist eigentlich die klarste: das void Schlüsselwort zeigt an, dass ein Ausdruck folgt, dessen Wert nicht verwendet wird.

Alternativen zum IIFE Grundmuster
+function (daten) {
   // beliebiger Code
}(daten);

-function (daten) {
   // beliebiger Code
}(daten);

void function (daten) {
   // beliebiger Code
}(daten);

Ob man dem Funktionsobjekt Parameter übergibt oder ob sich die Daten einfach durch Zugriffe auf den äußeren Scope beschafft, ist von den äußeren Umständen abhängig und teils auch eine Frage des eigenen Programmierstils. Wenn Sie einen IIFE mit Parametern sehen möchten, den Sie erst nach wiederholtem Lesen anfangen zu begreifen, schauen Sie sich den Sourcecode von jQuery an. Durch Parametrierung wird dort neutralisiert, in welche Umgebung jQuery eingebunden wird und durch welchen Modul-Loader das geschieht.

Hier ein Beispiel, wie ein IIFE mit seiner Closure ein gekapseltes Modul für einen Button-Klick Zähler bildet.

Klickzähler mit sofort ausgeführtem Funktionsausdruck (IIFE)
(function () {
    var zähler = 0;
    document.getElementById("button1")
            .addEventListener("click", function() {
               zähler++;
               alert("Das war Klick Nr. " + zähler);
            });
}());

Die Variable zähler existiert nur innerhalb der anomymen Funktion. Die Closure der anonymen Funktion, die an addEventListener übergeben wird, hält den Scope der Rahmenfunktion so lange am Leben, wie der Button existiert (entfernen kann man den Listener nicht, dafür müsste man das Funktionsobjekt der Listener-Funktion speichern).

Durch die umgebende anonyme Funktion werden die lokalen Variablen und Funktionen vor anderen JavaScripten „versteckt“, da sie außerhalb deren Geltungsbereich liegen. Sie sind lediglich Teil des Geltungsbereichs, der für den Aufruf der anonymen Funktion des IIFE gebildet wurde, und existeren genau so lang wie dieser.

Das ebenso hier vorgestellte Tutorial TicTacToe als Browsergame bedient sich dieser Vorgehensweise.

Kapselung mit einem ECMAScript 2015 Scope

Mit ECMAScript 2015, Codename „Harmony“, wurden die neuen Schlüsselwörter const und let eingeführt. Sie deklarieren Variablen, aber im Gegensatz zu var tun sie das nicht auf Ebene eines Funktionsscopes, sondern nur innerhalb des durch { und } gebildeten Blocks, in dem sie sich befinden.

Blockscope von const oder let
if (2 > 1) {
   let xy = 3;
}
xy = xy + 1;       // ReferenceError im strict mode
console.log(xy);   // undefined

Die Variable xy existiert nur innerhalb des Blocks, der zum if Statement gehört. Allerdings sind Blöcke nicht an andere Statements gebunden, sie können auch für sich existieren. Das macht sie zu einem leichtgewichtigen Ersatz für IIFEs:

Klickzähler mit Block-Scope
{
  let zähler = 0;
  document.getElementById("button1")
          .addEventListener("click", function() {
            zähler++;
            alert("Das war Klick Nr. " + zähler);
          });
}

Das Beispiel tut das gleiche wie die IIFE-Version im vorherigen Abschnitt, und kapselt genauso streng. Es wird aber kein eigener Funktions-Scope benötigt.

Beachten Sie: Ein Blockscope kapselt ausschließlich mit let und const deklarierte Variablen. Variablen, die mit var deklariert werden, und Funktionen, die als Funktionsstatement erzeugt werden, befinden sich im Funktions-Scope, nicht im Blockscope.

Siehe auch