Categorizing values in JavaScript is quirky. This blog post explains the quirks and one approach to fixing them. To understand everything, it helps to be familiar with how values are categorized in JavaScript. If you aren’t, consult
[1].
Problems
Categorizing values in JavaScript is problematic in several ways.
Three different mechanisms for basic tasks
You need three different mechanisms, depending on what you want to check:
- Distinguishing primitives from each other and from objects: use typeof [1].
- Determining which constructor an object is an instance of: use instanceof [1].
- Finding out whether a value is an array, even if it comes from another frame: use Array.isArray() [1].
JavaScript programmers shouldn’t have to know how these values differ. The language normally does a good job with hiding the differences and people tend to intuitively handle the different kinds correctly.
Objects that are not instances of Object
Some objects are not instances of
Object:
> Object.create(null) instanceof Object
false
> Object.prototype instanceof Object
false
You can use
typeof to check whether a value is an object, but that’s a bit complicated (see below).
typeof is quirky
The
typeof operator has several well-known quirks that can’t be fixed, because that would break existing code:
- typeof null is 'object'.
- The type of objects is 'object', except for functions, whose type is 'function'.
Hence, using
typeof to check whether a value is an object is a bit complicated:
function isObject(value) {
return (typeof value === 'object' && value !== null) ||
(typeof value === 'function');
}
The function in use:
> isObject(Object.create(null))
true
> isObject(Object.prototype)
true
> isObject(null)
false
> isObject('xyz')
false
The future: more value objects
In the future, we’ll probably get
more kinds of value objects (beyond current primitives): at least large integers, possibly even user-defined value object types. Hence, categorization will become even more tricky. For reasons of backward compatibility, we won’t be able to change
typeof to handle these cases.
One possible solution
The plan is to extend the
instanceof operator so that it also takes over all of
typeof’s duties. As we can’t do that, we’ll instead implement the following function:
function isInstance(value, Type) {
...
}
isInstance() will be explained in two steps. Its complete code is available as a
gist.
Step 1: an extensible protocol
Given the function call
isInstance(value, Type)
If
Type has a method
hasInstance, then return
Type.hasInstance(value). Otherwise, return
value instanceof Type.
function isInstance(value, Type) {
if (typeof Type.hasInstance === 'function') {
return Type.hasInstance(value);
} else {
return value instanceof Type;
}
}
Step 2: more types
We then complete JavaScript’s type hierarchy with more entries:
ValueType
Primitive
PrimitiveBoolean
PrimitiveNumber
PrimitiveString
(Future: user-defined value types)
ReferenceType
(Objects that are not an instance of Object)
Object
(Subtypes of Object)
The additions are only used for categorization and all have a method
hasInstance. For example:
isInstance.PrimitiveBoolean = {
hasInstance: function (value) {
return typeof value === 'boolean';
}
};
Examples
In action,
isInstance looks like this:
> isInstance(Object.create(null), Object)
false
> isInstance(Object.create(null), isInstance.ReferenceType)
true
> isInstance('abc', isInstance.PrimitiveString)
true
Open issues
We haven’t solved the problem of objects coming from other frames. It does not look like there will ever be a simple solution for this.
ECMAScript 6
The
current draft of the ECMAScript 6 specification allows extending
instanceof via the well-known symbol @@hasInstance, in a manner similar to what has been described above. However, the left-hand side of
instanceof must still be an object.
Reference
- Categorizing values in JavaScript