Type Checking in JavaScript is Slightly Screwed
JavaScriptâs dynamic typing is good and bad at the same time. Itâs good because you donât have to indicate the variableâs type. Itâs bad because you can never be sure about the variableâs type.
typeof
operator determines the 6 types in JavaScript:
typeof 10; // => 'number'
typeof 'Hello'; // => 'string'
typeof false; // => 'boolean'
typeof { a: 1 }; // => 'object'
typeof undefined; // => 'undefined'
typeof Symbol(); // => 'symbol'
As well, instanceof
checks the constructor of an instance:
class Cat { }
const myCat = new Cat();
myCat instanceof Cat; // => true
But some behavior of typeof
and instanceof
can be confusing. The edge cases are wiser to know in advance.
This post describes the pitfalls and remedial workarounds of using typeof
and instanceof
.
1. The type of null
typeof myObject === 'object'
would tell you if myObject
is an object type. Letâs try that in an example:
const person = { name: 'batman' };
typeof person; // => 'object'
typeof person
is 'object'
because person
holds a plain JavaScript object.
Variables that hold objects, sometimes, could be empty. In such case you would need null
value. Here are a few use-cases:
- You can use
null
to skip indicating configuration objects - You can initialize with
null
the variables that later will hold objects. - When a function cannot construct an object for some reason, it can return
null
For example, str.match(regExp)
method returns null
if no regular expression matches occur:
const message = 'Hello';
message.match(/Hi/); // => null
Can you use typeof
to differentiate an existing object from a null
missing object?
Unfortunately, you canât:
let myObject = null;
typeof myObject; // => 'object'
myObject = { prop: 'Value' };
typeof myObject; // => 'object'
typeof
with an existing object and with null
evaluates to 'object'
.
âThe history of typeof nullâ describes this bug in detail.
A good approach to detect if a variable has an object, and no null
values, is this:
function isObject(value) {
return typeof value === 'object' && value !== null;}
isObject({}); // => true
isObject(null); // => false
In addition to checking that value
is an object: typeof value === 'object'
, you also explicitely verify for null: value !== null
.
2. The type of an array
If you try to detect if a variable contains an array, the first temptation is to use typeof
operator:
const colors = ['white', 'blue', 'red'];
typeof colors; // => 'object'
However, the type of the array is an 'object'
too. While technically an array is an object, thatâs slightly confusing.
The correct way to detect an array is to use explicitely Array.isArray()
:
const colors = ['white', 'blue', 'red'];
const hero = { name: 'Batman' };
Array.isArray(colors); // => true
Array.isArray(hero); // => false
Array.isArray(colors)
returns a boolean true
, indicating that colors
is an array.
3. Falsy as type check
undefined
in JavaScript is a special value meaning uninitialized variable.
You can get an undefined
value if you try to access an uninitialized variable, non-existing object property:
let city;
let hero = { name: 'Batman', villain: false };
city; // => undefined
hero.age; // => undefined
Accessing the uninitialized variable city
and a non-existing property hero.age
evaluates to undefined
.
To check if a property exists, and undefined
being falsy, you might have the tempration to use object[propName]
in a condition:
function getProp(object, propName, def) {
// Bad
if (!object[propName]) { return def;
}
return object[propName];
}
const hero = { name: 'Batman', villain: false };
getProp(hero, 'villain', true); // => true
hero.villain; // => false
object[propName]
evaluates to undefined
when propName
doesnât exist in object
. if (!object[propName]) { return def }
guards for missing properties.
hero.villain
property exists and is false
. However, the function incorrectly returns true
when accessing villan
prop value: getProp(hero, 'villain', true)
.
undefined
is a falsy value. As well as false
, 0
, ''
and null
.
Donât use falsy as a type check of undefined
. Explicitly verify if the property exists in the object:
typeof object[propName] === 'undefined'
propName in object
object.hasOwnProperty(propName)
Letâs improve getProp()
function:
function getProp(object, propName, def) {
// Better
if (!(propName in object)) {
return def;
}
return object[propName];
}
const hero = { name: 'Batman', villain: false };
getProp(hero, 'villain', true); // => false
hero.villain; // => false
if (!(propName in object)) { ... }
condition correctly determines if the property exists.
Logical operators
I think itâs better to avoid using logical operator ||
as a default mechanism. My reading flow breaks when I see it:
const hero = { name: 'Batman', villain: false };
const name = hero.name || 'Unknown';
name; // => 'Batman'
hero.name; // => 'Batman'
// Bad
const villain = hero.villain || true;
villain; // => true
hero.villain; // => false
hero
has a property villain
with value false
. However the expression hero.villain || true
evaluates to true
.
The logical operator ||
used as a default mechanism to access properties fails when the property exists and has a falsy value.
To default when the property does not exists, better options are the new nullish coalescing operator:
const hero = { name: 'Batman', villan: false };
// Good
const villain = hero.villain ?? true;
villain; // => false
hero.villain; // => false
Or destructuring assignment:
const hero = { name: 'Batman', villain: false };
// Good
const { villain = true } = hero;
villain; // => false
hero.villain; // => false
4. The type of NaN
The integers, floats, special numerics like Infinity
, NaN
are of the type number.
typeof 10; // => 'number'
typeof 1.5; // => 'number'
typeof NaN; // => 'number'
typeof Infinity; // => 'number'
NaN
is a special numeric value created when a number cannot be created. NaN is an abbreviation of not a number.
A number cannot be created in the following cases:
// A numeric value cannot be parsed
Number('oops'); // => NaN
// An invalid math operation
5 * undefined; // => NaN
Math.sqrt(-1); // => NaN
// NaN as an operand
NaN + 10; // => NaN
Because of NaN
, meaning a failed operation on numbers, the check of numbers validity requires an additional step.
Letâs make sure that isValidNumber()
function guards against NaN
too:
function isValidNumber(value) {
// Good
return typeof value === 'number' && !isNaN(value);}
isValidNumber(Number('Z99')); // => false
isValidNumber(5 * undefined); // => false
isValidNumber(undefined); // => false
isValidNumber(Number('99')); // => true
isValidNumber(5 + 10); // => true
In addition to typeof value === 'number'
, itâs wise to verify !isNaN(value)
for NaN
.
5. instanceof and the prototype chain
Every object in JavaScript references a special function: the constructor of the object.
object instanceof Constructor
is the operator that checks the constructor of an object:
const object = {};
object instanceof Object; // => true
const array = [1, 2];
array instanceof Array; // => true
const promise = new Promise(resolve => resolve('OK'));
promise instanceof Promise; // => true
Now, letâs define a parent class Pet
and its child class Cat
:
class Pet {
constructor(name) {
this.name;
}
}
class Cat extends Pet {
sound = 'Meow';
}
const myCat = new Cat('Scratchy');
Now letâs try to determine the instance of myCat
:
myCat instanceof Cat; // => true
myCat instanceof Pet; // => true
myCat instanceof Object; // => true
instanceof
operator says that myCat
is an instance of Cat
, Pet
and even Object
.
instanceof
operator searches for objectâs constructor through the entire prototype chain. To detect exactly the constructor that has created the object look at the constructor
property of the instance:
myCat.constructor === Cat; // => true
myCat.constructor === Pet; // => false
myCat.constructor === Object; // => false
Only myCat.constructor === Cat
evaluates to true
, indicating exactly the constructor of the myCat
instance.
6. Key takeaways
The operators typeof
and instanceof
perform the type checking in JavaScript. While they are generally simple to use, make sure to know the edge cases.
A bit unpexpected is that typeof null
equals 'object'
. To determine if a variable contains a non-null object, guard for null
explicitely:
typeof myObject === 'object' && myObject !== null
The best way to check if the variable holds an array is to use Array.isArray(variable)
built-in function.
Because undefined
is falsy, you might be tempted to use it directly in conditionals. But such practice is error-prone. Better options are prop in object
to verify the property existence, nullish coalescing object.prop ?? def
or destructuring assignment { prop = def } = object
to access potentially missing properties.
NaN
is a special value of type number created by an invalid operation on numbers. To be sure that a variable has a âcorrectâ number, itâs wise to use a more detailed verification: !isNaN(number) && typeof number === 'number'
.
Finally, remember that instanceof
searches for the constructor of the instance through the prototype chain. Without knowing that, you could get a false-positive if you verify a childâs class instance with the parent class.
What JavaScript type checking pitfalls do you know?
Quality posts into your inbox
I regularly publish posts containing:
- Important JavaScript concepts explained in simple words
- Overview of new JavaScript features
- How to use TypeScript and typing
- Software design and good coding practices
Subscribe to my newsletter to get them right into your inbox.