ECMAScript 5, die nächste Version von JavaScript – Teil 6: Function.prototype.bind

Achtung: dieser Beitrag ist alt! Es kann gut sein, dass seine Inhalte nicht mehr aktuell sind.

Das letzte wirklich wichtige Feature von ES5, das wir noch nicht behandelt haben, ist die bind()-Methode von Function.prototype. Diese nützliche Funktion ist noch nicht in vielen Browsern nativ vorhanden, doch da der nötige Polyfill nur 20 Zeilen lang ist und ihn alle vernünftigen JavaScript-Bibliotheken (jQuery, MooTools und viele weitere) von Haus aus mitbringen, gibt es keinen Grund, bind() nicht einzusetzen.

Welches Problem löst bind()?

Innerhalb einer JavaScript-Funktion findet man immer (so gut wie immer – im Strict Mode nicht zwingend) ein Variable namens this vor, die das der Funktion zugehörige Objekt referenziert. Je nachdem wie man eine Funktion aufruft, ist das mal das eine, mal das andere Objekt:

var a = {
    foo: function(){
        console.log(this); // Hier ist "this" das Objekt "a"
    }
}

console.log(this); // Hier ist "this" das globale Objekt

function Make_B(){
    this.foo = function(){
        console.log(this); // Hier ist "this" das frisch erstellte Objekt "b"
    }
}
var b = new Make_B()
b.foo();

Wenn man eine Funktion via call() oder apply() aufruft, kann man bestimmen, was this sein soll und damit einige sehr clevere Dinge programmieren. Was aber, wenn wir bei der Erstellung der Funktion festlegen wollen, was this ist? Angenommen man wollte sich ein Widget programmieren, das bei einem Klick auf einen Button eine activate()-Funktion auslöst:

function Widget(){

    this.init = function(){
        var button = document.getElementById('clickme');
        button.onclick = function(){
            this.activate(); // Das geht schief - "this" ist hier der Button!
        }
    }

    this.activate = function(){
        alert('Widget aktiviert!')
    }

    this.init();

}

var my_widget = new Widget();

In diesem Fall referenziert this das Element, auf das wir den Event Listener setzen, also den Button. Das kann unter Umständen sinnvoll sein, hier wäre es uns aber lieber, wenn this unsere Widget-Instanz wäre. Für genau diese Fälle führt ES5 die bind()-Methode für Funktionen ein.

Was genau macht bind()?

Ruft man die bind()-Methode auf einer Funktion auf, wird eine neue Funktion zurückgegeben, die mit der Ursprungsfunktion so gut wie identisch ist, nur das this der neuen Funktion ist genau das Objekt, das man als erstes Argument an bind() übergeben hat. So lässt sich unser Widget-Problem einfach lösen:

function Widget(){

    this.init = function(){
        var button = document.getElementById('clickme');
        button.onclick = function(){
            this.activate(); // Jetzt geht es!
        }.bind(this);        // "this" der Funktion ist jetzt das "this" des Widgets!
    }

    this.activate = function(){
        alert('Widget aktiviert!')
    }

    this.init();

}

var my_widget = new Widget();

Dadurch, dass wir an den Eventhandler des Buttons bind(this) anhängen, wird das äußere this (das Widget) auch innen verwendet. bind() ist eine Methode von Function.prototype und steht damit (Browserunterstützung vorausgesetzt) allen Funktionen zur Verfügung.

Was kann bind() noch?

Zwar ist das Ändern von this der wohl häufigste Anwendungsfall von bind(), doch beim Erstellen der neuen Funktion kann auch die Liste der Argumente verändert werden. Jedes Argument von bind(), das auf das erste this-Argument folgt, wird als zusätzliches Argument vorne an die Argumentliste der Ursprungsfunktion angehängt:

// Gibt aus: ["A", "B", "C"]
var alt = function(){
    console.log(arguments);
}
alt("A", "B", "C");

// Gibt aus: ["foo", "bar", "A", "B", "C"]
var neu = alt.bind(null, "foo", "bar");
neu("A", "B", "C");

Das ist eine sehr nützliche Funktion für das Kapern von fremden Methoden. Wenn wir uns an die defineProperty()-Methode aus Teil 3 der Serie zurückerinnern, gab es dort das Problem, dass defineProperty() eine Methode des Object-Objekts und nicht von Object.prototype ist. Heißt also:

// Das hier hätten wir gern
meinObjekt.defineProperty('foo', bar);

// Aber das hier müssen wir schreiben
Object.defineProperty(meinObjekt, 'foo', bar);

Wie Molily in den Kommentaren von Teil 3 ganz richtig bemerkte, ist es aber mit nur einer Zeile Code möglich, mittels bind() ein defineProperty() für meinObjekt anzulegen:

meinObjekt.defineProperty = Object.defineProperty.bind(null, meinObjekt);

Das this ist in diesem Fall egal und daher null, aber dadurch, dass wir meinObjekt immer vor die Argumentliste von defineProperty() hängen, macht die Funktion auch als Methode von meinObjekt genau das, was sie soll.

Browserkompatibilität und Polyfills

An der Browserfront sieht es vergleichsweise mittelprächtig aus, denn nur die modernsten Surfprogramme (Firefox 4+, IE 9, Chrome) unterstützen bind() nativ. Aber es gibt auch zwei gute Nachrichten: erstens rüstet jede vernünftige JavaScript-Bibliothek (jQuery, MooTools, viele weitere) die Funktionalität von bind() nach und zweitens lässt sich selbst in Abwesenheit einer JS-Lib ein einfacher Polyfill schnell einbauen. Dieser ist zwar nicht völlig identisch mit der nativen Implementierung von bind(), was aber in 99,99% aller Fälle kein Problem sein dürfte.

Kommentare (0)

Noch keine Kommentare zu diesem Artikel.

Die Kommentarfunktion ist seit Juli 2014 deaktiviert, weil sie zu sehr von Suchmaschinenoptimierern für manuellen Spam mißbraucht wurde. Wenn du Anmerkungen oder Fragen zum Artikel hast, schreib mir eine E-Mail oder melde dich via Twitter.

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