Der beliebte Fragen-beantwort-Service von Erklärbär Enterprises meldet sich zurück, heute mit CSS-Filtern, HTML5-Datenbanken und zwei Fragen zu ECMAScript 6. Wenn auch euch Fragen zu Frontend-Themen wie HTML5, CSS3, JavaScript, Tools und ähnlichem habt, dann schickt sie mir: eine E-Mail oder ein Tweet genügen! Die Fragen werden beantwortet, gesammelt und wenn vier Antworten zusammen gekommen sind, folgt die Veröffentlichung.

Cross-Browser-Graustufen für Bilder?

Gibt es eine zuverlässige Möglichkeit (auch für den IE) Bilder via CSS oder JavaScript grau einzufärben?

In jedem modernen Browser mit Ausnahme der Internet-Explorer-Familie ist ein Graustufen-Effekt mit CSS-Filtern (Can I Use) ein Kinderspiel. Mit Filtern kann man jeden denkbaren SVG-Filter auf jedes beliebige Element in einer Webseite anwenden und für die häufigsten Anwendungsfälle wie eben Graustufen gibt es eine praktische Kurzschreibweise, bei der man sich den SVG-Part ersparen kann (Demo auf Codepen):

img {
  -webkit-filter: grayscale(100%);
  filter: grayscale(100%);
}

Dummerweise machen die neueren Internet Explorer nicht mit. Die IE 6-9 kann man mit einer vergleichsweise simplen Code-Transformation, wie sie in Schepps CSS-Filters-Polyfill vollführt wird, bedienen, denn diese unterstützen eine proprietäre Syntax mit ähnlichen Fähigkeiten wie filter. Allerdings wurde diese proprietäre Syntax im Zuge des großen Aufräumens aus dem IE 10 entfernt, so dass es dort keine Möglichkeit gibt, Graustufen-Filter mit CSS allein umzusetzen.

Einzig denkbarer Ausweg: das <canvas>-Element. Dieses funktioniert auch im IE9 und könnte genutzt werden, um normale <img>-Elemente zu klonen und sie im Zuge dessen in Graustufen zu konvertieren. Eine kleine Demo gibt es hier. Die Haken an dieser Methode sind die JavaScript-Abhängigkeit und natürlich, dass sich hiermit nur Bild-, Canvas- und Video-Elemente in Graustufen darstellen lassen – CSS-Filter funktionieren auf allen Elementen.

Was ist Tail Recursion? Und was hat das mit ECMAScript 6 zu tun?

Was ist „Tail Recursion“ und warum liest man im ES6-Kontext ständig davon?

Gibt man einer heutigen JavaScript-Engine eine Aufgabe, die viel Rekursion beinhaltet, bekommt man häufig einen Fehler der Marke maximum call stack size exceeded oder too much recursion vorgesetzt. Das liegt einfach darin begründet, dass der Call Stack, der die Liste der sich einander aufrufenden Funktionen beinhaltet, nicht beliebig groß werden kann. Beispiel:

function f(x){
  x++;
  if(x === 5000000){
    return 'Fertig nach ' + x + ' Aufrufen von f()';
  }
  return f(x);
}

console.log(f(0));
// Error: Maximum call stack size exceeded

Hier ruft f() immer wieder sich selbst auf und der Browser muss sich für jeden Aufruf das jeweils übergebene x und vieles mehr merken. Der Call Stack wächst und wächst, denn als fertig gilt ein Aufruf von f() erst, wenn alle enthaltenen f()-Aufrufe fertig sind. So wird der Call Stack immer größer, bis er irgendwann ein ein Limit stößt und es laut knallt.

Allerdings muss es in diesem Beispielcode eigentlich nicht zwangsläufig knallen, denn die Rekursion findet in f() als allerletzte Aktion statt – es handelt sich um einen soganannten Tail Call. Solche Tail Calls können durch die den Interpreter oder Compiler einer Programmiersprache so optimiert werden, dass sie keinen Extra-Platz auf dem Call Stack benötigen und viel tiefere Rekursion möglich wird. Optimiert könnte das obrige Programm genau so gut wie folgt aussehen:

function f(_x){
  var _again = true;
  _function: while(_again){
    _again = false;
    var x = _x;
    x++;
    if(x === 5000000){
      return 'Fertig nach ' + x + ' Aufrufen von f()';
    }
    _x = x;
    _again = true;
    continue _function;
  }
}

console.log(f(0));

Die rekursive Konstruktion wird zu einer Schleife umgebogen, belastet den Call Stack nicht mehr und kann daher wesentlich länger laufen. Voraussetzung für derartige Optimierungen ist lediglich, dass die Rekursion wirklich die aller-allerletzte Aktion in der fraglichen Funktion ist.

Mit ECMAScript 6 hat das Ganze nur insofern zu tun, als dass ab dieser Version die Optimierung von Tail Calls für alle JavaScript-Engines verbindlich vorgeschrieben sein wird. Bis das in allen Browsern der Fall ist, klappt es bei so einfacheren Fällen wie dem obrigen Beispiel auch mit Babel.

SQL-Datenbank im Browser?

Ich möchte zur Sicherung der Offline-Funktionalität meine Webapp mit einem MySQL-Backend synchronisieren. Hierbei müssten auch Abfragen mit JOINS möglich sein, also über mehrere Tabellen. Gibt es eine passende Browsertechnologie hierfür?

Eine SQL-Datenbank im Browser gibt es nicht und wird es nie geben. Die Spezifaktion für die Web SQL Database ist seit 5 Jahren tot und die Browserunterstützung wird nicht mehr besser werden – eher schlechter. Die moderne Alternativ-Technologie Indexed DB (Specs, Can I use) hingegen ist gesund und munter, ist aber auch eine NoSQL-DB, bei der es prinzipbedingt Dinge wie Joins nicht gibt.

Die Lösung besteht wie so oft darin, JavaScript auf das Problem zu werfen. Libraries wie Lovefield implementieren auf Basis der Indexed DB eine Query-Sprache, die zwar nicht ganz SQL ist, aber ungefähr das gleiche auszudrücken vermag.

ES6: Arrow Functions in Klassen?

Wie kann ich in ES6 Arrow Functions in Klassen benutzen? Ich habe eine Klassen-Methode getBar(), die immer das this des von der Klasse erzeugten Objekts benutzen soll. Arrow Functions scheinen in Klassen syntaktisch nicht zugelassen zu sein, aber ich möchte gerne Klassen-Methoden als Callbacks verwenden können ohne ständig fooInstance.getBar.bind(fooInstance) schreiben zu müssen.

Zur Verdeutlichung: das Ziel ist, dass das Folgende funktioniert:

class Foo {
  constructor(){
    this.bar = 'test';
  }
  getBar(){
    console.log(this.bar);
  }
}

var f = new Foo();
setTimeout(f.getBar, 1000);

Ohne f.getBar.bind(f) oder eine ähnliche Konstruktion wird das allerdings nicht funktionieren – Arrow Functions können hier nicht helfen. Das Problem ist, dass die Klassensyntax von ES6 nur syntaktischer Zucker für die althergebrachten JS-Prototypen ist. Der obrige ES6-Code entspricht (grob) dem folgenden ES5:

function Foo(){
  this.bar = 'test';
}
Foo.prototype.getBar = function(){
  console.log(this.bar);
}

var f = new Foo();
setTimeout(f.getBar, 1000);

Hier sieht man sofort, dass getBar() nicht auf ein this festgenagelt werden kann, da das Ziel-this zum Zeitpunkt der Definition getBar() nicht bekannt ist bzw. getBar() als Prototypen-Methode mit den unterschiedlichen this der unterschiedlichen Foo-Instanzen arbeiten muss. Es handelt sich hier also um keine syntaktische, sondern eine ganz grundsätzliche Hürde.

Was also tun? Am besten tatsächlich brav jedes Mal f.getBar.bind(f) verwenden, denn das ist am einfachsten und am deutlichsten. Alternativ könnte man natürlich auch jeder Instanz eine eigene getBar()-Methode geben, die sich das this per Closure merkt:

class Foo {
  constructor(){
    this.bar = 'test';
    this.getBar = function(){
      console.log(this.bar);
    }.bind(this)
  }
}

var f = new Foo();
setTimeout(f.getBar, 1000);

Insgesamt ist bind() aber schon die sauberere Lösung.

Weitere Fragen?

Auch eure Fragen zu HTML(5), CSS(3), 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.