Ein Wiedergänger unter den JavaScript-Patterns ist die Idee, man könnte doch die Prototypen von Built-Ins (z.B. Function, Array) und Host Objects (wie Element.prototype) erweitern, um Library-Funktionalität direkt an Objekte anzubauen. Macht man das, handelt man sich mehrere mögliche Probleme ein. Möglicherweise gibt es (jetzt oder in Zukunft) anderen Code im Projekt, der sich darauf verlässt, dass Objekte nur exakt jene Eigenschaften haben, die der Standard vorgibt. Oder möglicherweise gibt es (jetzt oder in Zukunft) Code im Projekt, der eine genau gleich benannte Erweiterung in die gleichen Objekte einbauen möchte. Oder möglicherweise programmiert eine halbwegs erfolgreiche Library und belegt damit in Zukunft einen Eigenschafts-Namen, den zukünftige ECMAScript-Standards gern benutzt hätten. Letzteres ist in ES6 passiert – dort sollte Array.prototype.contains() eingeführt werden, doch weil Mootools diesen Namen einst besetzt hat, musste die Funktion für ES6 in Array.prototype.includes() umbenannt werden.

Dieses Problem kann man schrittweise abschwächen; macht man die neu an das Objekt gebaute Eigenschaft non-enumerable, taucht sie z.B. nicht mehr in for-in-Schleifen auf und reduziert damit etwas ihr eigenes Kollisionspotenzial. Aber eine wirklich halbwegs saubere Lösung gibt es erst mit den in ECMAScript 6 eingeführten Symbols.

In Variablen gespeicherte Strings kann man bekanntlich als Eigenschaftsnamen für Objekte einsetzen:

// Das ging schon immer
var o = {};
var x = 'Foo';
o[x] = 42;

// Das ist neu in ES6
var p = {
  [x]: 42
};

In ECMAScript 6 gibt es eine spezielle Art von Objekten, die anstelle von Strings als Eigenschaftsnamen verwendet werden können. Diese Objekte heißen Symbols und können genau wie die Strings im obrigen Beispiel benutzt werden:

var o = {};
var x = Symbol();
o[x] = 42;

var p = {
  [x]: 42
};

Symbols funktionieren auf den ersten Blick also wie Strings, haben aber mehrere entscheidende Unterschiede. So sind Symbols sind per Definition einzigartig. Code, der keinen Zugriff auf ein Symbol hat, kann die damit gesetzten Werte nicht ohne weiteres auslesen:

var o = (function(){
  var x = Symbol();  
  return {
    [x]: 42
  };
})();

// Wie soll man an den Wert kommen?

// Das funktioniert nicht, Symbols are einzigartig
var x = Symbol();
console.log(o[x]); // undefined

Symbols sind außerdem immer non-enumerable und tauchen daher nicht in for-in-Schleifen auf. Sie verweigern sich auch jedem Versuch, sie in einen String zu verwandeln um irgendwie an den „wahren Wert“ hinter einem Symbol zu kommen oder es zu klonen. Symbols selbst sind der einzig wahre Wert und sie sind per Definition einzigartig.

Symbols kann man für viele nützliche Dinge gebrauchen. Die ECMAScript-Spezifikationen nutzen Symbols extensiv für den internen Aufbau der Sprache. Was den Hausgebrauch angeht sind Symbols super, um „private“ Instanzvariablen auf Objekten unterzubringen, ohne diese selbst in Closures zu platzieren:

// Keine Chance, die 42 zu überschreiben!
var o = (function(){
  var x = Symbol();  
  return {
    [x]: 42,
    getX: function(){
      return this[x];
    }
  };
})();

Aber auch öffentliche Symbols können nützlich sein. So könnte eine Library ein öffentliches Symbol anbieten, das Nutzer der Library nutzen können, um bestimmte Funktionen gezielt zu überschreiben. Theoretisch könnte diese Rolle auch ein String übernehmen, aber diese String-Eigenschaft wäre (wenn ich explizit anders definiert) in for-in-Schleifen sichtbar und sie würde bis in alle Ewigkeit eben jenen String-Namen als Objekt-Eigenschaft belegen und ggf. so manches Problem hervorrufen – man denke nur an __proto__. Symbols sind hier die sauberere Variante.

Symbols sind freilich nicht völlig privat: mit Object.getOwnPropertySymbols() kann man sich die Liste aller Symbol-Eigenschaften auf einem Objekt ausgeben lassen und darüber iterieren:

var o = (function(){
  var x = Symbol();  
  return {
    [x]: 42,
    getX: function(){
      return this[x];
    }
  };
})();

Object.getOwnPropertySymbols(o).forEach(function(sym){
  console.log(o[sym]); // > 42
});

Dass diese Funktion existiert, ist recht sinnvoll. Wer einen komplett privaten Wert haben möchte, hat ja bereits mit Closures das passende Werkzeug an der Hand. Symbols sind geheim genug für den Alltagsgebrauch und Object.getOwnPropertySymbols() macht das Debuggen und Testen von Objekten um einiges einfacher.

Allerdings bedeutet die Existenz von Object.getOwnPropertySymbols() auch, dass es eben nicht möglich ist, vorhandene Objekt zu erweitern und dabei jede Chance auf Kollisionen oder Missbrauch auszuschließen. Zwar gibt es nun keinen String-Eigenschaftsnamen mehr, der überschrieben werden könnte, aber mit einem Extra-Symbol wird plötzlich die Iterations-Schleife über das von Object.getOwnPropertySymbols() zurückgegebene Array länger, alte oder neue Symbols stehen nicht mehr an dem Index, an dem sie vorher waren und schon kann wieder eine ganze Menge kaputtgehen.

Und so bleibt es trotz Symbols bei der JavaScript-Grundregel Nummer 1: Prototyp-Objekten, die man nicht selbst gebaut hat, darf man nicht modofizieren, weil man nicht zu 100% ausschließen kann, das die Modifikation jetzt oder in Zukunft fremden Code kaputt macht.

Laut Kompatibilitätstabelle funktionieren Symbols heutzutage in Firefox, Chrome, dem kommenden nächsten Microsoft-Browser und sind hinter einem Flag auch in Node.js verfügbar. Transpiler wie Traceur und Babel versuchen ebenfalls Symbols zu implementieren, schaffen das bisher aber nicht perfekt.