Autobinding Class Methods in Javascript ES6

by James Johnson

Updated on 03-24-16 to fix an error

Most developers who work with Javascript have dealt with callbacks losing their desired this context when the callback is called. This post details a simple way to autobind all class methods to eliminate this problem.

Callback Example Without Binding

In the example below we have a Person class that describes a person (me). A person instance can sayHi() by writing something to the console. This say hi method is used as a callback to a click event of a button:

<html>
    <head>
        <script src="jquery.js" type='text/javascript'></script>
        <script>
            "use strict";
            class Person {
                constructor(fullName, age) {
                    this.fullName = fullName;
                    this.age = age;
                }

                sayHi() {
                    console.log(this.fullName + " says hi!");
                }
            }

            function onload() {
                var me = new Person("James Johnson", 153);
                var btn = document.getElementById("testBtn");
                btn.addEventListener("click", me.sayHi);
            }
        </script>
    </head>
    <body onload="onload()">
        <button id="testBtn">me.sayHi()</button>
    </body>
</html>

What is actually written to the console is: undefined says hi!. Obviously the value of this when the callback is called is not what we expected it to be.

Callback Example With Binding

Now, if we were to bind the sayHi() method before adding it as the event listener, the sayHi() method should be called with the correct this context:

//...
function onload() {
    var me = new Person("James Johnson", 153);
    var btn = document.getElementById("testBtn");
    btn.addEventListener("click", me.sayHi.bind(me));
}
//...

If we use a bound sayHi() function as the callback, the output is as expected: James Johnson says hi!

Callback Example With Autobinding

In my experience, if I am working with classes and using class methods as callbacks in Javascript, I rarely need a callback to not be bound to the class instance. Since this is usually the case, we can create a base class that will automatically bind all functions to the correct this context:

class AutoBind {
    constructor() {
        this.__doBind(this);
    }

    __doBind(currCls) {
        var names = Object.getOwnPropertyNames(currCls.__proto__);
        for(var memberName of names) {
            // skip getters/setters
            var descriptor = Object.getOwnPropertyDescriptor(currCls.__proto__, memberName);
            if(descriptor && (descriptor.get || descriptor.set)) {
                continue;
            }

            if(typeof(this[memberName]) == "function" && memberName != "constructor") {
                this[memberName] = this[memberName].bind(this);
            }
        }

        if(currCls.constructor.name != "AutoBind") {
            this.__doBind(currCls.__proto__);
        }
    }
}

class Person extends AutoBind {
    constructor(fullName, age) {
        super():
        //...
    }
    //...
}

Now when we pass in the me.sayHi method as the callback to the click event, the correct this context will be used because sayHi will already have been bound by the AutoBind constructor.

This should also (and does in my testing) work with many levels of inheritance:

//...
class Mutant extends Person {
    sayBlargh() {
        console.log(this.fullName + "s!Uykj--.. says BLARGH!");
    }
}

function onload() {
    var mutantMe = new Mutant("James Johnson", 153);
    var btn = document.getElementById("testBtn");
    btn.addEventListener("click", mutantMe.sayBlargh);
}

Which outputs: James Johnsons!Uykj--.. says BLARGH!

For those who may have read this earlier, I updated the autobind class to work better with many levels of inheritance.