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.