typeof
and instanceof
: simplifying dynamic type checksThis blog post describes a technique for making instanceof
applicable to more values (on the right-hand side). Specifically, for primitive values.
typeof
vs. instanceof
In JavaScript, you have to choose when it comes to checking the type of a value. The rough rule of thumb is:
typeof
checks if a value is an element of a primitive type:if (typeof value === 'string') ···
instanceof
checks if a value is an instance of a class or a constructor function:if (value instanceof Map) ···
(Additionally, value.constructor
and value.constructor.name
are occasionally useful.)This is already less than ideal, because you have to keep the difference between primitive values and objects in mind, which you can often ignore, otherwise.
Alas, a few quirks make things even more complicated:
typeof null
is 'object'
, not 'null'
. This is considered a bug.typeof
distinguishes between objects and functions (which are also objects):> typeof {}
'object'
> typeof function () {}
'function'
This quirk, combined with the previous quirk means that there is no simple way to check for object-ness via typeof
.Object
:> Object.create(null) instanceof Object
false
instanceof
for primitive values Given the class PrimitiveNumber
, the following code configures for which values x
the expression x instanceof PrimitiveNumber
returns true
. It does so by implementing a static method for PrimitiveNumber
whose key is the public symbol Symbol.hasInstance
.
class PrimitiveNumber {
static [Symbol.hasInstance](x) {
return typeof x === 'number';
}
}
console.log(123 instanceof PrimitiveNumber); // true
TypeRight is a minimal library for dynamic type checking. Among other features, it uses the approach shown in the previous example to implement the following pseudo-classes. Their only purpose is to be right-hand sides of the instanceof
operator:
PrimitiveUndefined
PrimitiveNull
PrimitiveBoolean
PrimitiveNumber
PrimitiveString
PrimitiveSymbol
TypeRight does not currently provide a class for checking if a value is an object, but that could easily be added.
Building on these foundations, you can use TypeRight to check if the parameters of a function have the proper types:
import * as tr from 'type-right';
function dist(x, y) {
tr.force(x, tr.PrimitiveNumber, y, tr.PrimitiveNumber);
return Math.hypot(x, y);
}
dist(3, 4); // 5
dist(3, undefined); // TypeError
Two proposals for handling type checks are currently at stage 0. That means that they describe ideas that may or may not be explored further in the future.
The proposal “ECMAScript Pattern Matching Syntax”, by Brian Terlson and Sebastian Markbåge, introduces the key Symbol.matches
to make values “matchable”:
match (val) {
MyClass:
console.log('val is an instance of MyClass');
}
The property could either be generated automatically or added manually, as follows (line A).
class PrimitiveNumber {
static [Symbol.matches](x) { // (A)
return x instanceof this;
}
}
Builtin.is()
and Builtin.typeOf()
The proposal “Builtin.is
and Builtin.typeOf
” by James M. Snell introduces several mechanisms for type checking.
Builtin.is(value1, value2)
checks if value1
and value2
refer to the same builtin constructor. It takes into consideration that value1
and value2
may come from the current realm or another realm:
> Builtin.is(Date, vm.runInNewContext('Date'))
true
> Builtin.is(Date, Date)
true
Builtin.typeOf()
can be seen as an extension typeof
that works for both primitive values and built-in classes:
Builtin.typeOf(undefined); // 'undefined'
Builtin.typeOf(null); // 'null'
Builtin.typeOf(123); // 'number'
Builtin.typeOf(new Number()); // 'Number'
Builtin.typeOf([]); // 'Array'
Builtin.typeOf(new Map()); // 'Map'
// The builtin counts, not the user-defined class
class MyArray extends Array {}
Builtin.typeOf(new MyArray()); // 'Array'
constructor
property of instances” in “Speaking JavaScript”typeof null
” in “Speaking JavaScript”typeof
: Categorizing Primitives” in “Speaking JavaScript”Symbol.hasInstance
” in “Exploring ES6”