JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.
Detailed in this post are common JavaScript data type checks, pitfalls and idiomatic workarounds.
Classic JavaScript Data Type Checks
Here’s a collection of the most common data type checks in JavaScript. Whether you want to check if a variable contains a Date, a Promise, a plain old JavaScript object or an Array, it’s all here.
Everything from primitive types like number, boolean, string to detecting functions.
Check if JavaScript variable contains an object
typeof
does output 'object'
for objects.
It also does so for null
and Arrays.
const object = {};
console.log(typeof object); // 'object'
console.log(typeof null); // 'object'
console.log(typeof []); // 'object'
console.log(object instanceof Object); // true
console.log(null instanceof Object); // false
console.log([] instanceof Object); // true
What’s more, much like in the case of Arrays, if there is inter-frame communication, it tends to share objects and arrays (see JavaScript array type check - “is array” vs object in-depth). Therefore, checking whether something is a simple object vs an instance of a class is difficult.
In JavaScript you’ll notice that everything is an Object, and when you try to access a property that doesn’t exist, it’ll fail quietly (ie. return undefined
):
console.log('aaaaa'.noProperty); // undefined
console.log([].nothing); // undefined
In idiomatic JavaScript code, we leverage this property to be just defensive enough, for example if we expect an object that has a growl
method, but something else might be passed in:
function safeGrowl(anything) {
if (anything.growl) {
anything.growl()
}
}
safeGrowl('hello'); // nothing
safeGrowl({ growl() { console.log('Growl!') }}); // Growl!
The moral of the story is: don’t check that something is an object, check that it has the properties you need (that’s what’s called duck typing).
Check if a value is a string in JavaScript
For strings, we can use a typeof
check.
Much the same as for object checks, JavaScript won’t fail loudly when you try to use something as a string that isn’t a string, it will tend to just coerce it or call .toString
on it.
const string = 'Hello World';
console.log(typeof string); // 'string'
// Implicit coercion to string using templates
const helloCount = 2;
const newString = `Hello number ${helloCount} at ${new Date('2019-06-23T21:00:26.861Z')}`;
console.log(newString);
// 'Hello number 2 at Sun Jun 23 2019 22:00:26 GMT+0100 (British Summer Time)'
This works with dates, number. For arrays and other objects that don’t directly implement a toString method, I would suggest using JSON.stringify.
const myArray = ['a', 'b', 'c'];
const mySimpleObject = { key: 'value' };
console.log(`${myArray} ${mySimpleObject}`); // 'a,b,c [object Object]'
console.log(`${JSON.stringify(myArray)} ${JSON.stringify(mySimpleObject)}`)
// '["a","b","c"] {"key":"value"}'
Check if a value is a JavaScript Number/Integer
JavaScript Numbers are a bag of fun. They’ve got a similar gotcha to object
checks, that’s the NaN
(Not a Number) value. NaN
usually is the output of trying to do arithmetic where one of the operands is not a Number.
The quirks of NaN
is that it’s not equal to itself, and it’s actually a Number, just like Infinity
and - Infinity
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(typeof NaN); // 'number'
console.log(typeof Infinity); // 'number'
console.log(typeof -Infinity); // 'number'
console.log(typeof 123); // 'number'
Checking that a Number is not NaN
(Not A Number)
One NaN
check would just be:
const a = NaN;
function isNotANumber(maybeNotANumber) {
return maybeNotANumber === maybeNotANumber;
}
isNotANumber(a); // true
The recommended approach is the following, there’s a built-in Number.isNaN
function:
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('abc')); // false
console.log(Number.isNaN(1234)); // false
console.log(Number.isNaN(123.11)); // false
console.log(Number.isNaN(true)); // false
The difference between Number.isNaN
and the isNaN
global is that Number.isNaN
checks that the value passed in is a Number and it is NaN
.
The older global isNaN
function just goes for the literal check that something is not a number.
console.log(isNaN(NaN)); // true
console.log(isNaN('abc')); // true
console.log(isNaN(1234)); // false
console.log(isNaN(123.11)); // false
console.log(isNaN(true)); // false
Check if a JavaScript variable contains an Integer
To check that JavaScript variable (or value) is an integer, we can use Number.isInteger
:
console.log(Number.isInteger(123)); // true
console.log(Number.isInteger(12.3)); // false
console.log(Number.isInteger(123.0)); // true
Check if a JavaScript variable contains a useable Number value
To check that we’ve got a useable input value, we should check that the type is number
and that the value is not NaN:
function isValidNumber(maybeNumber) {
return typeof maybeNumber === 'number' && !Number.isNaN(maybeNumber);
}
console.log(isValidNumber('aaaaa')); // false
console.log(isValidNumber(NaN)); // false
console.log(isValidNumber(123)); // true
console.log(isValidNumber(1.23)); // true
Check if a value is a boolean
As with JavaScript string and number data types, in JavaScript the pattern is to assume something is a boolean (or cast it to boolean) rather than checking that it’s a boolean. That’s because in JavaScript we can use logical operators with non boolean values due to the loose typing, this is usually explained through the concept of “truthiness” and “falsiness”.
When JavaScript is expecting a boolean and it is given one of the values below, it will always evaluate to “falsy”
The values in question (falsy values) are: false
, 0
, ''
(or other empty string), null
and undefined
. Any other value will be evaluated to true.
There are some cases where false
means something other than undefined
, in that case, it’s possible to check that a value is falsy and a boolean by using the typeof
:
console.log(typeof true); // 'boolean'
console.log(typeof false); // 'boolean'
Check if a variable contains an Array
To check if a JavaScript variable is an Array, there’s a built-in Array.isArray
.
The fun gotcha with JavaScript Arrays is that they’re just objects.
console.log(([]) instanceof Object); // true
console.log(typeof []); // 'object'
A way to duck-type an Array is to use existence of a .length
property. However this can be pretty weak since there’s nothing enforcing that Arrays should be the only type to have a .length
property. The pattern tends to look like so:
function processList(maybeArray) {
if (!maybeArray.length) {
return []
}
return maybeArray.map(i => i); // literally copy
}
Now this code doesn’t actually check that maybeArray
is an Array. It sort of does that but in the same line of code ie. !maybeArray.length
, it also states that maybeArray
must have a non-falsy length, ie. in the case where it is in fact an Array, it shoul also not have length 0 (must not be empty).
It’s trivial to trick the above and make it crash on .map
with for example using the following data: { length: 'aaaa' }
. That’s not the point, if consumers of this function are trusted, this kind of check can be fine.
Using Array.isArray however works as follows:
console.log(Array.isArray({})); // false
console.log(Array.isArray(new Map())); // false
console.log(Array.isArray(new Set())); // false
console.log(Array.isArray([])); // true
console.log(Array.isArray(new Array())); // true
For a closer look at the internals of how we check for Arrays, see JavaScript array type check - “is array” vs object in-depth. The big gotcha with built-in JavaScript data types like Array, Object and Date in JavaScript is that communicating between frames means the constructors and therefore instanceof
checks don’t work.
Check if an object is an instance of a specific class/constructor function
Say you’ve got a set variable and you want to check that it’s a React Component, you can do:
import React, { Component } from 'react';
const myComp = new Component();
function isReactComponent(maybeComponent) {
return maybeComponent instanceof Component;
}
isReactComponent(myComp);
// true
isReactComponent({});
// false
This also works with constructor functions:
function Dog (name) {
this.name = name
}
const max = new Dog('Max');
console.log(max instanceof Dog); // true
Another interesting thing is it works all the way up the prototype chain/class hierarchy:
console.log(({}) instanceof Object); // true
console.log((new Dog) instanceof Object); // true
Check if an object is an Error
Error
is just a constructor/class. So in the same way we could check for React.Component
or Dog
class:
function isError(maybeError) {
return maybeError instanceof Error;
}
isError(new Error('Something went wrong')); // true
isError(new EvalError()); // true
isError(new InternalError()); // true
isError(new RangeError()); // true
isError(new ReferenceError()); // true
isError(new SyntaxError()); // true
isError(new TypeError()); // true
isError(new URIError()); // true
See more about Fundamental Objects on MDN.
Check for a valid JavaScript Date string (parseable date-string)
function isValidDateString(maybeDateString) {
return !Number.isNaN(Number(new Date(maybeDateString)));
}
console.log(isValidDateString('abcd')); // false
console.log(isValidDateString(1234)); // true
console.log(isValidDateString('2019-06-23T22:00:26.861Z')); // true
The above function doesn’t actually check whether something is a valid string but whether it’s convertible to a valid date.
For most intents and purposes, it will catch dodgy date strings, and has the benefit of not being unreadable at the cost of allow number timestamps to be passed in. A more apt name might be isConvertibleToDate
. Disallowing numbers would just be a case of adding a typeof maybeDateString === 'string'
.
Check for a valid JavaScript Date
To check whether or not something is a valid, we’ll just take the same approach as checking whether it’s convertible to a date
function isValidDateObject(maybeDate) {
return (
typeof maybeDate === 'object' &&
!Number.isNaN(Number(new Date(maybeDate))
);
}
isValidDateObject('abc'); // false
isValidDateObject(1234); // false
isValidDateObject('2019-06-23T22:00:26.861Z'); // false
isValidDateObject(new Date('2019-06-23T22:00:26.861Z')); // true
You could also apply the instanceof
approch:
function isValidDateObject(maybeDate) {
return maybeDate instanceof Date;
}
isValidDateObject('abc'); // false
isValidDateObject(1234); // false
isValidDateObject('2019-06-23T22:00:26.861Z'); // false
isValidDateObject(new Date('2019-06-23T22:00:26.861Z')); // true
This has some cross-frame issues and you never know when someone might mess with the global Date
to replace it with their own custom, non-standard version.
Check if a JavaScript variable is a Promise
Promise checks are done using instanceof
with all the usual cross-frame or custom implementation caveats:
console.log({} instanceof Promise); // false
With Promises there’s also the issue for then-ables. Which for most intents and purposes may as well be Promises but which won’t pass our above check.
In async/await-enabled environments, you’ll also note that await
-ing a function that doesn’t return a Promise doesn’t cause any unintended side-effects, the return value of the function (even if it’s not an async function), can be stored the same way as you would if you hadn’t await
-ed.
Check if a JavaScript variable is a function
As mentioned on the MDN Web Docs JavaScript is a “programming language with first-class functions”. First-class functions, just means functions are treated like any other variable.
console.log(typeof (() => {})); // 'function'
console.log(typeof function () {}); // 'function'
function myFunc () {}
console.log(typeof myFunc); // 'function'
Refer to the object example to see how an idiomatic “run this function if it exists” would look.
Debugging JavaScript Data Type Issues
What does each of the following look like when it’s console.log
-ed?
One of the big issues is that console.log
tends to stringify whatever object it’s passed using its .toString()
method. A lot of the time, a combination of pattern matching, logging out typeof
and logging out a JSON.stringify
-ed version of the object gives good results.
Figuring out if something is a Promise
Forgot to await
a function that returns a Promise (including an async
function).
You usually want to do something with the output of the Promise:
A promise that’s supposed to return a list
const fetchList = async () => ['first-item'];
async function doSomething() {
const [firstItem] = fetchList();
}
doSomething()
// UnhandledPromiseRejectionWarning:
// TypeError: fetchList is not a function or its return value is not iterable
A promise that’s supposed to return an object
const fetchObj = async () => ({ property: 'value' });
async function doSomething() {
const obj = fetchObj();
console.log(obj.property);
console.log(obj);
console.log('done')
}
doSomething()
// undefined
// Promise {
// { property: 'value' },
// and so on
Debugging Array vs Array-like
There are a few Array-like objects in JavaScript for example arguments
, NodeList
s (output of document.querySelectorAll
).
The first thing to do is to just log them out:
const anchors = document.querySelectorAll('a');
console.log(anchors); // { "0": {}, "1": {} }
function multiVariateFn() {
console.log(arguments)
}
multiVariateFn(1, 2, 3); // [Arguments] { '0': 1, '1': 2, '2': 3 }
Compare those outputs to:
console.log([1, 2, 3]); // [1, 2, 3]
Here is the old-school method of converting them to regular Array
-s (which means you can use Array.forEach/.map etc.):
const anchors = Array.prototype.slice.call(
document.querySelectorAll('a'),
0
);
function multiVariateFn() {
const args = Array.prototype.slice.call(arguments, 0);
return args.reverse();
}
console.log(multiVariateFn(1, 2, 3, 4)); // [4, 3, 2, 1]
console.log(multiVariateFn('a', 'b', 'c')); // ['c', 'b', 'a']
The ES6 approach would look something closer to this, they take advantage of array spread syntax and rest parameters syntax respectively.
const anchors = [...document.querySelectorAll('a')];
function multiVariateFn(...args) {
return args.reverse();
}
console.log(multiVariateFn(1, 2, 3, 4)); // [4, 3, 2, 1]
console.log(multiVariateFn('a', 'b', 'c')); // ['c', 'b', 'a']
A more conservative approach could leverage Array.from
(see Array from MDN Web Docs):
const anchors = Array.from(document.querySelectorAll('a'));
function multiVariateFn() {
return Array.from(arguments).reverse();
}
console.log(multiVariateFn(1, 2, 3, 4)); // [4, 3, 2, 1]
console.log(multiVariateFn('a', 'b', 'c')); // ['c', 'b', 'a']
Source Materials - Further Reading
While creating this JavaScript Data Type Check guide, I took inspiration from some of the top relevant posts:
- http://tobyho.com/2011/01/28/checking-types-in-javascript/
- https://webbjocke.com/javascript-check-data-types/
- https://ultimatecourses.com/blog/understanding-javascript-types-and-reliable-type-checking
As well as the MDN Web Docs for JavaScript.
Top comments (0)