ECMAScript 6: Symbols

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.

JavaScript: parseInt() vs. parseFloat()

An den Tagen, an denen ich mich für ganz besonders lustig halte, verfasse ich manchmal Tweets wie diesen hier:

Wenn Tweets mit Witzen ein bisschen die Runde machen, wird der Tweet-Autor immer zum Empfänger zahlreicher Witz-Erklärungen. Da in diesem Fall aber keine der angebotenen Erklärungen etwas mit der Wahrheit hinter parseFloat() und parseInt() zu tun hatten, fühle ich mich genötigt eine kleine Erklärung des behandelten Phänomens nachzuschieben.

Wie parseFloat() funktioniert

Anders als man vielleicht zunächst glauben würde, haben parseFloat() und parseInt() in ihrer Funktionsweise unter der Haube nur wenig gemein. Die ECMAScript-Spezifikationen beschreiben die Funktion parseFloat() wie folgt:

The parseFloat function produces a Number value dictated by interpretation of the contents of the string argument as a decimal literal.

Der konkrete Algorithmus für die Interpretation des an parseFloat() übergebenen Strings sieht vor, dass, nachdem er um Whitespace am Anfang bereinigt wurde, der String von links nach rechts nach einem StrDecimalLiteral durchsucht wird. Nach dem ersten Zeichen, das nicht in die Definition von StrDecimalLiteral passt, wird die Suche beendet; so ergibt parseFloat(' 42.23€') die Number 42.23. Ein StrDecimalLiteral ist nichts weiter als ein bestimmter Brocken String-Syntax, bestehend aus einem StrUnsignedDecimalLiteral und einem optionalen Vorzeichen. Und da die Definition von StrUnsignedDecimalLiteral neben den Zahlen 0 bis 9, Kommata und Exponenten auch den String Infinity beinhaltet, produziert parseFloat('Infinity') die Number Infinity.

Wie parseInt() funktioniert

Die Kurzbeschreibung von parseInt() liest sich wie folgt:

The parseInt function produces an integer value dictated by interpretation of the contents of the string argument according to the specified radix.

Der Knackpunkt ist „according to the specified radix“. Grundsätzlich macht parseInt() das gleiche wie parseFloat(), nämlich einen String, der mit Zahlzeichen beginnt, ab der ersten Nicht-Zahl (im Falle von parseFloat() auch ab einem eventuellen Komma) abzuschneiden. Aber parseInt() ist in der Lage, Zahlen mit jeder Basis zwischen 2 und 36 zu verarbeiten, wozu ein zweiter Parameter angegeben werden kann. Das Ergebnis von parseInt('FF', 16) ist also 255. Wird der zweite Parameter nicht explizit angegeben, verwendet parseInt() seit ECMAScript 5 immer 10 (falls der zu parsende Input-String nicht gerade mit 0x beginnt). Beim Aufruf von parseInt("Infinity") sucht die Funktion also nach Ziffern von 0 bis 9 am Anfang des Strings (da ja Baisis 10 verwendet wird), findet keine, kann aus dem Eingabestring keine Zahl extrahieren und gibt gezwungenermaßen NaN zurück.

Ein Infinity-kompatibles parseInt()

Ein parseInt(), das mit Infinity klar kommt, ist einfach zu bauen, wenn man bereit ist sich auf die Basis 10 festzulegen. Das Problem besteht einzig darin, den Sonderfall Infinity abzufangen, bevor das eigentliche parseInt() auf den Plan tritt. Die hierfür scheinbar prädestinierte Funktion isFinite() würde allerdings auch für Eingaben wie "1a" mit false antworten – schließlich ist der String "1a" keine endliche (oder sonst irgendeine) Zahl. Deshalb sollte man den Eingabewert erst mittels parseFloat() in eine JavaScript-Number inklusive Infinity verwandeln, Unendlichkeit ausfiltern und schlussendlich das Ergebnis von parseFloat() in parseInt() hineinfüttern:

function parseIntInfinity(input){
  var number = parseFloat(input);
  if(!isFinite(number)){
    return number;
  }
  return parseInt(number, 10);
}

parseIntInfinity("1"); // > 1
parseIntInfinity("1F"); // > 1
parseIntInfinity("F1"); // > NaN
parseIntInfinity("Infinity"); // > Infinity
parseIntInfinity("-InfinityXXX"); // > -Infinity

Mit einer anderen Basis als 10 würde eine solche Funktion schon etwas schwieriger zu bauen. Je nach Basis könnte der String "Infinity" für eine ganze Reihe von Werten stehen, beginnend bei 1461559270678 (Basis 36) bis runter zu 18 (Basis 19, wo nach dem I abgeschnitten wird). Wie auch immer man Unendlichkeit repräsentieren möchte: mit demString "Infinity" geht es nicht, wenn parseInt() mitspielen soll.

Erklärbär-Termine für April, Mai und Juni 2015

Für das nächste Quartal bietet Erklärbär-Tours zwei kleinere Workshops im Rahmen von Konferenzen und zwei große Wechtech-Schulungen an:

  • 26. - 28. Mai in München: Moderne Frontendentwicklung (HTML5, CSS3, JavaScript). HTML5 … und dann? Die Schulung „Moderne Frontendentwicklung“ ist der Nachfolger meiner klassischen HTML5-Schulung und hebt erfahrene Webentwickler auf das nächste Level. Der Kurs behandelt die seit HTML5 neu hinzugekommenen Webstandards (z.B. Web Components), bespricht Tools und Best Practices, gibt Tipps für den Einsatz neuer Features in heutigen Browsern und bietet auch einen Ausblick in die weitere Zukunft der Web-Plattform.
  • 7. Juni in Berlin: Workshop zu Web Components auf der Webinale. Das Webseiten-Modul der Zukunft ist ein selbstdefinierter HTML-Tag und dieser Workshop beantwortet hierzu alle Fragen. Wie funktionieren Web Components? Welche Tools gibt es? In welchen Browsern kann man Web Components bereits einsetzen? In einem großen Praxisteil können die Teilnehmer selbst ein paar eigene Komponenten schreiben.
  • 18. Juni in Nürnberg: Workshop zu Techniken für asynchrones JavaScript. Auf der Developer Week 2015 erkläre ich alles, was es zu asynchronem JavaScript zu wissen gibt. Angefangen von einem Blick unter die Browser-Motorhaube über Alltags-Techniken wie Callbacks und Promises geht es bis hin zu abgefahreneren Techniken wie asynchrone Funktionen und Communicating Sequencial Processes.
  • 24. - 26. Juni in München: JavaScript MasterClass. Meine aktuelle Bestseller-Schulung bringt erfahrenen Entwicklern mit gefährlichem JS-Halbwissen richtiges JavaScript bei. Vom Grundlagen-Crashkurs über OOP und funktionale Techniken bis hin zu spezielleren Themen wie ECMAScript 6, Makros und Metaprogrammierung ist alles dabei.

Termine unpassend, Orte alle zu weit weg und Programme nicht genehm? Ich komme auch gerne mit einem maßgeschneiderten Talk oder Workshop vorbei – mich kann man ganz einfach mieten!

Fragen zu HTML5 und Co beantwortet 19 - Flexbox-Umbrüche, magische Body-Backgrounds, ES6-Promises, Main-Element

Lange keine Fragen mehr beantwortet! Das lag nicht an einem Mangel an Input, sondern vielmehr daran, dass ich vor lauter JavaScript-Schulungen kaum zum Schreiben (von Blogposts, nicht von Antworten auf E-Mails) gekommen bin. Mit dieser Sammlung aus Problemen rund um HTML, CSS und JS hat das zum Glück ein Ende. Wenn auch ihr eine Frage zu Frontend-Technologien habt, nur her damit: eine E-Mail oder ein Tweet genügen!

Flexbox-Umbrüche erzwingen

Kann man Flexboxen gezielt umbrechen lassen um zum Beispiel nach jedem dritten Element einen Umbruch zu erzwingen?

Kurze Antwort: in der Theorie sollte es ganz einfach gehen, in der Praxis funktioniert es nicht ganz so gut. Die Flexbox-Spezifikationen sehen Umbrüche mit den ganz normalen CSS-Umbruch-Eingenschaften vor:

A break is forced wherever the CSS2.1 page-break-before/page-break-after or the CSS3 break-before/break-after properties specify a fragmentation break.

Ein Umbruch nach jedem dritten Kindelement sollte demnach kein Problem sein:

.container > :nth-child(3n) {
  break-after: always;
  page-break-after: always;
}

Der Haken an der Sache ist, dass einerseits break-after in so gut wie keinem Browser funktioniert, andererseits page-break-after in Chrome und Internet Explorer keinen Einfluss auf Flexbox zu haben scheint. Nur im Firefox funktionieren die Umbrüche wie vorgeschrieben.

ES6-Promises inspizieren?

Die Promises von ECMAScript 6 bieten offenbar keine Möglichkeit, ihren Status auszulesen. Wie kann ich trotzdem herausfinden, ob ein Promise resolved oder rejected ist?

Fast alle denkbaren Features von Promises lassen sich mit Hilfe der guten alten then()-Methode umsetzen. Die einfachste Lösung für das Inspektions-Problem besteht darin, eine Wrapperfunktion für Promise() zu bauen. Diese gibt ein ganz normales Promise zurück, das aber durch die Wrapper-Funktion mit Eigenschaften wie isResolved und isRejected ausgestattet ist:

// http://jsbin.com/funepe/2/edit?js,console
function SuperPromise(executor){
  var promise = new Promise(executor);
  promise.isResolved = false;
  promise.isRejected = false;
  promise.then(function(){
    promise.isResolved = true;
  }, function(){
    promise.isRejected = true;
  });
  return promise;
}

Die Wrapper-Funktion sorgt mit ihren then()-Callbacks dafür, dass bei Statusänderungen die Eigenschaften isResolved und isRejected ein Update bekommen:

var myPromise = new SuperPromise(function(resolve){
  setTimeout(resolve, 1000);
});

console.log(myPromise.isResolved); // > false

myPromise.then(function(){
  console.log(myPromise.isResolved); // > true
});

setTimeout(function(){
  console.log(myPromise.isResolved); // > true
}, 1000);

Das ist die einfachste Lösung. Wer besonders fancy sein möchte, kann sich auch einen Promise-Proxy bauen (Firefox) oder, sobald Browser ES6-Klassen unterstützen, eine class SuperPromise extends Promise basteln, aber das Grundprinzip bleibt gleich.

Magische Body-Backgrounds?

Wie kann dieser Effekt sein? Ich gebe dem 0 Pixel hohen Body-Element einen Farbverlauf und die ganze Seite wird mit einem sich wiederholenden, 8 Pixel hohem Verlauf überzogen? Was geht da vor sich?

Bei diesem Effekt handelt es sich um eine Kombination aus verschiedenen CSS-Sonderregeln für ganz bestimmte Elemente. Wenn ein <html>-Element keinen eigenen CSS-Hintergrund hat, übernimmt es den Hintergrund vom <body>-Element (genau genommen die computed values). Das <body>-Element hat seinerseits einen Standard-Margin von 8 Pixeln, was (durch collapsing marging) auch dafür sorgt, dass das <html>-Element auch ohne jeden Inhalt 8 Pixel hoch ist.

Unser <html>-Element hat jetzt den Hintergrund vom <body>-Element sowie 8 Pixel Höhe durch den Margin des <body> erhalten; 100% Breite hat es als Block-Element sowieso. Der Farbverlauf wiederholt sich nun bis zum Ende der Seite, weil der Hintergrund des Wurzelelements einer Seite (im Falle von HTML <html>) als Hintergrund für die gesamte Renderingfläche verwendet wird. Dabei wird der Hintergrund jedoch immer noch so gerendert, als wäre er für für sein eigentliches Element (<html>) bestimmt und ggf. wiederholt, bis die ganze Renderingfläche bedeckt ist.

<main> oder nicht <main>, das ist hier die Frage

Vor dem Aufbau des HTML-Gerüsts meiner neuen Webseite habe ich mir den Quelltext vieler anderer Seiten angeschaut. Inzwischen wird kräftig Gebrauch von <header>, <section>, <artice>, <aside> und <footer> gemacht, aber kaum jemand nutzt <main>! Da wird fast überall immer noch ein <div>-Wrapper verwendet oder das <section>- oder <article>-Element per ID und WAI-ARIA-Role zum <main>-Element umgebaut. Kannst du mir mal erklären wieso man überwiegend so verfährt und nicht einfach <main> nutzt?

Ich nehme an, dass der Grund für den spärlichen <main>-Einsatz bei allen möglichen Webseiten (inklusive meiner eigenen Seite) der gleiche ist: das Element ist vergleichsweise neu! Während es <header>, <footer>, <section>, <artice>, <nav> und <aside> schon seit Anbeginn der Zeiten (und damit auch während des großen HTML5-Hypes) gab, ist <main> erst später dazugekommen und daher weniger verbreitet und weniger bekannt.

Das <main>-Element ist aber tatsächlich das semantisch korrekte Element für den Hauptinhalt, funktioniert in jedem Browser, ist seit HTML 5.0 offizieller Webstandard, hat eingebaute WAI-ARIA-Features und sorgt, verglichen mit einem umgebogenen <section>-Element für besser lesbaren Quelltext. Bei einem neuen Projekt würde ich es also auf jeden Fall einsetzen. Andererseits sind die wirlich messbaren Vorteile, die man durch den Einsatz von <main> erlangt, eher überschaubar, weswegen bestehende Seiten (wie auch meine eigene) es eher selten nachträglich einbauen. Es gilt die alte Informatiker-Faustregel: never change a running system.

Weitere Fragen?

Auch eure Fragen zu HTML5, JavaScript und anderen Webtechnologien beantworte ich gerne! Einfach eine E-Mail schreiben oder Twitter bemühen und ein bisschen Geduld mit der Antwort haben. Ansonsten kann man mich natürlich auch als Erklärbär zu sich kommen lassen.

Folgt mir!

Kauft mein Zeug!

Cover der HTML5-DVD
Infos auf html5-dvd.de, kaufen bei Amazon

Cover des HTML5-Buchs
Infos auf html5-buch.de, kaufen bei Amazon

Cover des CSS3-Buchs
Infos auf css3-buch.de, kaufen bei Amazon