Qubyte Codes

Interfaces for JavaScript

Published

I use instanceof a lot in JavaScript. It's very handy when writing unit tests. It's easier to do an instanceof check than it is to exhaustively probe an object.

Unfortunately instanceof usually means an object has been constructed. If the constructed object is coming from a third party library, or there is no access to the constructor, it can become fiddly.

This is why I'm excited about ES2015s Symbol.hasInstance. It allows you to tune the behaviour of instanceof for a class. Here's a minimal example of an interface class:

class PositiveInteger {
  constructor() {
    throw new Error('PositiveInteger is an interface class.');
  }

  static [Symbol.hasInstance](value) {
    if (typeof value !== 'number') {
      return false;
    }

    if (value < 0) {
      return false;
    }

    return value % 1 === 0;
  }
}

assert.ok(10 instanceof PositiveInteger);   // does not throw
assert.ok(-10 instanceof PositiveInteger);  // throws
assert.ok('hi' instanceof PositiveInteger); // throws
var positiveInt = new PositiveInteger();    // throws

The class above exists only to provide this instanceof check. A more interesting example might be a view. I assert that a view has an element, and render and remove methods. An interface class for this might be:

class View {
  constructor() {
    throw new Error('View is an interface class.');
  }

  static [Symbol.hasInstance](value) {
    if (!value) {
      return false;
    }

    if (typeof value.render !== 'function') {
      return false;
    }

    if (typeof value.remove !== 'function') {
      return false;
    }

    return value.element instanceof HTMLElement;
  }
}

Now view objects can come from any source as long as they have render and remove methods and an element. Objects which implement various interface classes also mean that these interface classes don't have to be in the same prototype chain. In other words, it gives you a way to use instanceof without invoking inheritance.

Sadly hasInstance will be one of the last ES2015 features to make it into browsers, so we'll have to wait a while before we can use it. See the compatibility table.