ponyfoo.com

Binding Methods to Class Instance Objects

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

There’s a number of different ways we can ensure class methods have access to this. After some controversy on Twitter last week, we quickly go over the most common approaches, discussing their advantages and drawbacks.

The problem is when we have a class method such as the following.

class Logger {
  printName (name = 'there') {
    this.print(`Hello ${name}`);
  }

  print (text) {
    console.log(text); 
  }
}

Then – for whatever reason – the printName method’s context changes, expectations aren’t met, exceptions are thrown, and kittens die.

const logger = new Logger();
const { printName } = logger;
printName();
// <- Uncaught TypeError: Cannot read property 'print' of undefined

Twitter drama began with André’s salty remarks about a competing front-end framework.

The problem lies, of course, with the fact that JavaScript doesn’t yet have semantics where we can easily mark every class method as bound to that class, which is how they probably should work.

Let’s look at our options.

Caveman Mode

In this scenario, we just bind methods by hand on the constructor.

class Logger {
  constructor () {
    this.printName = this.printName.bind(this);
  }

  printName (name = 'there') {
    this.print(`Hello ${name}`);
  }

  print (text) {
    console.log(text); 
  }
}

This is the least elegant solution, but it works. Drawbacks include having to keep track of which methods use this and need to be bound, or ensuring every method is bound, remembering to .bind new methods as they are added, and removing .bind statements for methods that are removed. Benefits include being explicit, and having no extra code involved.

Auto Bind

A similar but less painful approach is using a module that takes care of this on our behalf. Sindre’s auto-bind goes through an object’s methods and binds them to itself.

class Logger {
  constructor () {
    autoBind(this);
  }

  printName (name = 'there') {
    this.print(`Hello ${name}`);
  }

  print (text) {
    console.log(text); 
  }
}

This approach works well for classes, although we can’t escape the need for a constructor. The advantage is that we don’t have to keep track of every single method by name when binding them. At the same time, if we’re dealing with objects rather than classes, we need to ensure that autoBind gets called on the object after every method has been assigned to the object, or else some methods will be left unbound. Any methods added after autoBind gets called are unbound, and this means that in some situations autoBind is an even worse option than manually calling .bind on every method.

Proxies

A Proxy object could be used to intercept get operations, returning methods bound to the logger. Below we have a selfish function which takes an object and returns a proxy for that object. Any methods accessed through the proxy will be automatically bound to the object. A WeakMap is used to ensure we only bind the methods once, so that equality in proxy.fn === proxy.fn is preserved.

function selfish (target) {
  const cache = new WeakMap();
  const handler = {
    get (target, key) {
      const value = Reflect.get(target, key);
      if (typeof value !== 'function') {
        return value;
      }
      if (!cache.has(value)) {
        cache.set(value, value.bind(target));
      }
      return cache.get(value);
    }
  };
  const proxy = new Proxy(target, handler);
  return proxy;
}
const logger = selfish(new Logger());

The benefit of taking this approach is that methods on the target object will be bound regardless of whether they were added before or after the proxy object was created. The obvious drawback is that Proxy support is meager even when transpilers are taken into account.

Even if proxies were generally available, this solution would also be far from ideal. The difference lies in that libraries could potentially implement something like selfish so that components you hand over to the library would follow these semantics without you having to do anything. That said, the only real solution lies in the language moving forward, adding semantics for classes that have every method bound to itself by default.

We’re definitely better off than back when we had to write Logger.prototype.print – an implementation detail artifact that never made a lot of sense – and classes are a result of observing a pattern that was being repeated consistently, over time, and developing a solution. A private scope for classes where you can declare functions scoped to that class that aren’t class methods (but are available to every method) and other variables scoped to the class would be a huge step forward for classes.

Having bind semantics that are a bit less verbose, tedious, error-prone, or complicated, is mostly a matter of time. And when it comes to JavaScript, we all know that time flows faster. ⏳

What’s your preferred way of ensuring methods are bound to their host object? What native JavaScript semantics would you propose for objects where every method is bound to their host?

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (11)

Stuart van Beek wrote

I have been using the notation I have written here to bind a method to a class instance.

  • It’s only 5 characters longer than normal method notation
  • No need to run .bind and store the result in the constructor
  • No need for additional libraries
  • No need to rely on Proxy

Unfortunately, it only works in Babel and TypeScript (and possibly other transpilers) which transpile it to creating the function in the constructor and using _this instead of this. When running it in V8 directly, It will throw an error Uncaught SyntaxError: Unexpected token =. This is because it is not allowed to define class instance properties in the class definition. However if you are using a transpiler it is by far the best way to do it.

sminutoli wrote

Hi Nicolas, thanks for writing this (pun intented). The tricky thing about bind is that you’re making copies of the functions for every instance. Another workaround using class and constructor is using arrow functions directly there

constructor(){
    this.printName = (name = 'there')=> {
        this.print(`Hello ${name}`);
   }
}

The drawbacks of using bind or the => approach are…

  • you can’t re-bind a bounded function (so, you can’t borrow the method)
  • you can’t use the benefits of delegation, because you’re overriding the prototype implementation with your own bounded function

In that sense, doing explicit bindings saves unnecesary copies (you usually need the binding the one’s used as eventHandlers, auto bind binds them all).

In the DOM realm, the addEventListener implements EventListener interface, so you can suscribe an object to an event, mantaining the this reference. The famous library mentioned only allows functions to be passed as a handler, so binding seems the only solution.

I think your selfish implementation is the closest one to the expected behaviour (I wouldn’t do the binding, instead I’d try forwarding the message, fixing the this for each call).

The real issue here is when passing functions as a value, they loose the context. In other words, a function never belongs to an object (it’s not a method, it’s just a property which value refers to a function). Making this more stable/static kills the prototype nature of the language IMO.

Zlatko wrote

Alternative to constructor version of auto-bind, there’s the @autobind decorator:

https://www.npmjs.com/package/autobind

E.g.

class Logger {
@autobind
    printName(name = '') {
        this.print(name);
    }
    print(name) {
        console.log(`Hello ${name}`);
    }
}

Or the class-version:

    @autobind
class Logger {
 ....
}
Chris wrote

With es7 classProperties its as easy as

class Foo { bar = () => { console.log(‘hi’); } }

Rajasekharan Vengalil wrote

With TypeScript I find myself doing this pretty much all the time:

class Foo {
  public bar = () => {
    // yay, lexical 'this' y'all!
  }
}

The syntax is still weird though.

kris wrote

Yes this has been my fix for TypeSCript as well. At least its not that intrusive.

kris wrote

It may not be a popular option, but this was solved many times over in non 100% native JS implementations.

The earliest implementations I remember was Dustin Diaz’s Klass which I always felt was ahead of its time. Another great library which isn’t considered modern sexy JS is Coffeescript which has for years has had class extension and even static class methods. Of course typescript also has good Class with scopes and inheritance.

On my last project we decided to go 100% native Class implimentation. Every 4th line started with the this keyword, like your first example. It was a pain and a half.

Andrea Giammarchi wrote

I don’t find particular problematic the explicit bound definition within the constructor. It’s not hard to read, it’s not hard to customise (i.e. bind only what you need).

I’ve found everything else proposed here overkill though, so here my suggestion: use lazy bind evaluation. It works per instance, it doesn’t require Proxy or WeakMap (not so backward compatible and definitively slower), and it scale without compromising method access through the prototype.

You define bound methods once per class, and only when and if an instance access one of its methods it gets updated to its own bound version.

This guarantee best performance during instances creation, less memory usage and GC pressure that a WeakMap would require, more compatibility than a Proxy, down to IE9 (well, IE8 has getters/setters too but that’s another story)

function lazyBound(Class) {
    var proto = typeof Class === 'object' ?
        Class : Class.prototype;
    Object.getOwnPropertyNames(proto).forEach(function (name) {
        var
            descriptor = Object.getOwnPropertyDescriptor(proto, name),
            value = descriptor.value
        ;
        if (typeof value === 'function') {
            // set it up as getter, instead of value
            delete descriptor.value;
            delete descriptor.writable;
            descriptor.get = function () {
                // ignore methods borrowed via the prototype
                if (this === proto) return value;
                // or lazy define it once as own property
                // and return the bound value.
                return Object.defineProperty(
                    this,
                    name,
                    {value: value.bind(this)}
                )[name];
            };
            // update the class method definition
            Object.defineProperty(proto, name, descriptor);
        }
    });
    return Class;
}

// the example, it could be inline
// or after class definition
const Logger = lazyBound(class Logger {
  printName (name = 'there') {
    this.print(`Hello ${name}`);
  }

  print (text) {
    console.log(text); 
  }
});

const logger = new Logger();
const { printName } = logger;
printName();
Andrea Giammarchi wrote

P.S. a decorator that does per method or per entire class pretty much exactly what I’ve done in my previous function would also be a better approach. Too bad decorators are still not deployed as standard.

Fire-Dragon-DoL wrote

There is a not trivial issue with bind, which is: cloning becomes extremely tricky. Every cloned function will be bound to the original object, ending with crazy issues. Either you skip all functions (and you have to deal with nested objects) and re-clone them, or you overwrite them. In both cases, it kills js simplicity.

The recommendation for avoiding classes are probably right, put methods only in functions (js modules) and data only in objects, so no functions on object, except pure ones. Basically you eliminate all the js quirks and it becomes even a pleasant language in some way…

m3g4p0p wrote

The problem lies, of course, with the fact that JavaScript doesn’t yet have semantics where we can easily mark every class method as bound to that class, which is how they probably should work.

I beg to differ. The thing is that strictly speaking, there are no methods in JS. There are only pointers to functions, but these functions aren’t “owned” by any object. So with a statement like

const print = logger.print;

you’re explicitly putting that function in a new context. If you want to preserve the context, you’ll have to bind it like

const print2 = logger.print.bind(logger);

(which makes much more sense semantically than flatly binding this to this in the constructor). ;-) That’s not a problem to overcome, but a very distinct feature of JS – you can put any function into any context. Consider

const alerter = {
  printName: logger.printName,
  print(text) {
    alert(text);
  }
};

alerter.printName();

That’s a very convenient way to “borrow” methods. Functions can even be declared agnostic of their context, like e.g.

function printName(name = 'there') {
  this.print('Hello ' + name);
}

const printer = {
  printName,
  print(text) {
    window.print(text);
  }
};

printer.printName();

Re. prototypes – they actually make an awful lot of sense for exactly that reason. JS is a prototype-based language where only objects exist; the class keyword is just semantic sugar (mostly – referencing a parent with super wasn’t possible before).