Während andere Programmiersprachen Entwicklern eines ganzes Arsenal von Datenstrukturen zur Sammlungen von Objekten anbieten – Listen, Sets, Trees usw. – muss man sich in JavaScript seit jeher mit Arrays und Objekten begnügen. Diese magere Auswahl führt häufiger zu einem leisen Zähneknischen, denn die aus Bordmitteln gebauten Ersätze sind, wenn überhaupt machbar, umständlich und nicht besonders performant. Um an dieser Stelle für Linderung zu sorgen, führt ECMAScript 6 einen ganzen Haufen von neuen Cellections ein, von denen wir uns heute einmal Maps, Sets und Weak Maps genauer ansehen werden.

Maps

Maps sind ähnlich wie normale JavaScript-Objekte Paare aus Namen und Werten. Mit ihrer set()-Methode können neue Name-Wert-Paare in der Map gespeichert werden und mit get() können die Werte wieder ausgelesen werden:

var myMap = new Map();
myMap.set('foo', 42);
myMap.get('foo');      // 42

Der Clou hierbei: als Namen können alle JavaScript-Datentypen herhalten, nicht nur Strings:

var myMap = new Map();
myMap.set(42, 'foo');           // Number als key
myMap.set([1, 2, 3], 'bar');    // Array als key
myMap.set(function(){}, 'baz'); // Funktion als key
myMap.set(NaN, 'buh');          // Selbst NaN geht!

Damit wird es natürlich sehr viel leichter, Metadaten zu Objekten wie z.B. DOM-Nodes oder jQuery-Objekten zu verwalten. Weder ist es nötig, die Metadaten im Objekt selbst zu speichern (z.B. als data-*-Attribute in HTML-Elementen) noch muss man sich mit der Zuordnung eines Objekts zu einem Schlüssel in einem anderen Objekt abmühen – die Objekte selbst sind die Schlüssel!

Wichtig ist: als Schlüssel dienen wirklich die Objekte selbst und sie werden (fast) wie bei einem Vergleich mit === unterschieden.

// get() ergibt undefined - die Funktionen in Get und
// Set sind unterschiedliche Objekte
myMap.set(function(){}, 'foo');
myMap.get(function(){});

// get() ergibt "bar" - die Funktion in Get und Set
// sind jeweils das gleiche Objekt
var fn = function(){};
myMap.set(fn, 'bar');
myMap.get(fn);

Die einzigen Ausnahmen sind an dieser Stelle NaN, das als identisch zu NaN gewertet wird (obwohl eigentlich NaN !== NaN gilt) und +0 bzw. -0, die unterschieden werden (obwohl eigentlich +0 === -0 gilt).

Neben set() und get() haben Maps noch die folgenden drei Methoden:

  • myMap.has(key): Gibt an, ob unter dem Schlüssel key etwas gespeichert ist (Boolean)
  • myMap.delete(key): Löscht die unter dem Schlüssel key gespeicherten Daten
  • myMap.size(): Gibt die Anzahl der gespeicherten Name-Wert-Paare zurück

Eine Möglichkeit über Maps zu iterieren sehen die ECMAScript-Entwürfe zur Zeit nicht vor und auch kein Browser hat diesbezüglich etwas in petto. Dass in der Richtung noch nachgerüstet wird, ist aber nicht besonders unwahrscheinlich.

Sets

In Sets können wie in einem Array beliebige Daten abgelegt werden, wobei allerdings jeder Wert nur einmal im Set vorhanden sein kann. Mit der has()-Methode kann geprüft werden, ob ein Wert in einem Set vorliegt:

var mySet = new Set();
mySet.add(42);
mySet.has(42); // true

Ob ein Wert in einem Set vorhanden ist, ist eine einfache Ja/Nein-Frage. Führt man mehrmals mySet.add(42) aus, ändert sich nichts, denn der Wert 42 ist ja schon nach dem ersten Mal im Set vorhanden. Neben add() und has() kennen Sets noch die Methoden delete() zum Löschen eines Werts und size() zur Angabe der Anzahl der gespeicherten Datensätze.

Wie bei Maps auch werden die Werte in Sets mit === vergleichen mit den gleichen Ausnahmen für NaN sowie +0 und -0. Auch eine Möglichkeit über die gesammelten Daten zu iterieren fehlt Sets noch – Nachrüstung nicht ausgeschlossen.

Weak Maps

Weak Maps funktionieren wie normale Maps, verhindern aber nicht, dass ihre Schlüsselobjekte der automatischen Speicherbereinigung von JavaScript zum Opfer fallen. JS-Engines kümmern sich automatisch darum, Speicher für Objekte vorzuhalten und sie geben auch automatisch Speicher wieder frei, sobald ein Objekt nicht mehr gebraucht wird. Den Status „nicht mehr gebraucht“ erhält ein Objekt, sobald keine Variablen mehr auf es zeigen und es damit vom laufenden Programm nicht mehr erreicht werden kann. Im folgenden Beispielcode würde der vom Objekt belegte Speicher erst dann wieder frei gegeben, wenn die Variablen foo und bar nicht mehr auf das Objekt zeigen, dieses dadurch nicht mehr erreichbar und damit nicht mehr benötigt wird.

var foo = {};
var bar = foo;

Die gleiche Lage gibt es auch beim Einsatz von Maps:

var myMap = new Map();
var keyObj = { foo: 42 };

myMap.set(keyObj, 'foo'); // "foo" unter `keyObj` speichern
keyObj = undefined;       // `keyObj` referenziert nicht mehr das Objekt

Das Problem ist nur: wie werden wir jetzt das Objekt los, das unter keyObj gespeichert war? Da wir keine Referenz mehr in Form einer Variablen auf das Objekt haben, können wir myMap.delete() nicht benutzen. Andererseits wird das Objekt ja weiterhin von der Map referenziert und wird daher bis in alle Ewigkeit von der Speicherbereinigung verschont bleiben. Passiert so etwas häufiger, hat man ein hässliches Speicherproblem.

Weak Maps schaffen hier Abhilfe, denn sie referenzieren Objekte in ihren Keys nur schwach. Wenn die einzige verbliebene Referenz auf ein Objekt der Schlüssel in einer Weak Map ist, darf die Speicherbereinigung das betreffende Objekt entfernen. Nehmen wir also den Problemcode von oben und ersetzen die Map durch eine Weak Map, löst sich das Speicherproblem in Luft auf:

var myMap = new WeakMap();
var keyObj = { foo: 42 };

myMap.set(keyObj, 'foo'); // "foo" unter `keyObj` speichern
keyObj = undefined;       // `keyObj` referenziert nicht mehr das Objekt

Sobald keyObj nicht mehr das Objekt referenziert, ist die einzig verbliebene Referenz die in der Weak Map. Da diese Referenz nur schwach ist, wird das Objekt beim nächsten Speicherbereinigungsdurchlauf gelöscht und der Speicher freigegeben. Naturgemäß kennen Weak Maps anders als Maps keine size()-Methode und auch eine Iterationsmöglichkeit wird es nicht geben.

Unterstützung und Ausblick

Maps, Sets und Weak Maps sind in Chrome Canary (bei aktiviertem Strict Mode) und Firefox ab Version 13 (Weak Maps ab Version 6) vorhanden. In Chrome muss unter about:flags der Punkt „Enable Experimental JavaScript“, Firefox und Firefox Mobile unterstützen die neuen Features ohne weiteres zutun. In Node.js können Weak Maps mit dem Startparameter --harmony_weakmaps aktiviert werden.

Komplett fertig und stabil scheinen die neuen Datenstrukturen noch nicht zu sein, zumindest mit Iterationsmöglichkeiten für Sets und Maps ist noch zu rechnen. Andererseits gibt es nun in Chrome bzw. V8 und Spider Monkey (Firefox) bereits zwei Implementierungen – ganz so groß werden die Verwerfungen also wohl nicht mehr werden und zumindest dass die nächste ECMAScript-Version Maps, Sets und Weak Maps haben wird, scheint sicher. Weiterer Lesestoff: