JavaScript: Testing the type of a value

Updated . Posted . Visible to the public. Repeats.

Checking if a JavaScript value is of a given type can be very confusing:

  • There are two operators typeof and instanceof which work very differently.
  • JavaScript has some primitive types, like string literals, that are not objects (as opposed to Ruby, where every value is an object).
  • Some values are sometimes a primitive value (e.g. "foo") and sometimes an object (new String("foo")) and each form requires different checks
  • There are three different types for null (null, undefined and NaN) and each has different rules for equality.

Your project will often use a library like Lodash Show archive.org snapshot or Unpoly Show archive.org snapshot 's up.util Show archive.org snapshot that will hide this inconsistency behind functions like _.isBoolean(x) or _.isObject(x). However, I encourage every JavaScript developer to understand what these functions do under the hood. This way you will be able to understand code that does not use LoDash, e.g. when debugging a library or when working on a project without LoDash.

Booleans

This one is straightforward to test with typeof:

x = true
typeof x === 'boolean' // => true

Strings

Strings can exist as literal ("foo") and as an object (new String("foo")), hence we cannot rely on typeof:

typeof "foo" // => "string"
typeof new String("foo") // => "object"

Your test should check both forms:

x = 'foo'
typeof x === 'string' || x instanceof String // => true

Numbers

Numbers can exist as literal (123) and as an object (new Number(123)), hence we cannot rely on typeof:

typeof 123 // => "number"
typeof new Number(123) // => "object"

Your test should check both forms:

x = 123
typeof x === 'number' || x instanceof Number // => true

Functions

x = function() {}
typeof x === 'function' // => true

Note that classes (class Foo { ... }) are also functions. There is no easy way to test if an object is a class, especially when transpilation is involved.

An object with keys and values

When a developer talks about an "object" in JavaScript, she probably means a variable where you can assign values to keys. However, JavaScript has a different notion of what constitutes an "object". When a library has a function to test for an object, it detects the notion that is more useful to the developer.

Let's start with the easiest case, an Object or object literal. Here typeof returns "object", as expected:

x = {}
x.foo = 'bar'
x.foo // => 'bar'
typeof x // => 'object'

However, you can also assign key/values on functions:

x = function() {}
x.foo = 'bar'
x.foo // => 'bar'
typeof x // => 'function'

Finally JavaScript has a caveat that typeof null returns 'object'. No developer would consider this behavior useful, since you cannot get or set properties with null.

Taking into consideration all of the above, most libraries consider this to be a check for "object":

(typeof x === 'object' || typeof x === 'function') && (x !== null)

Arrays

In JavaScript, Arrays are objects where the indexes are keys, and the elements are values.

If you want to check that an object is an array, you can use the static Array.isArray() method:

Array.isArray([1, 2, 3]) // => true
Array.isArray({ foo: 'bar' }) // => false

Note that Array.isArray() returns false for array-like objects like jQuery collections or the NodeList Show archive.org snapshot returned by document.querySelectorAll().

Instances of a given class

Use instanceof to check if an object is an instance of the given class. It checks whether the object was constructed with the given constructor function.

class Foo {}

x = new Foo()
x instanceof Foo // => true

This also returns true if the object is an instance of the subclass of the given class:

class Foo {}
class Bar extends Foo {}

y = new Bar()
y instanceof Foo // => true

Note that there is also object.constructor.name, which returns the class name of the given instance as a string:

class Foo {}
x = new Foo()
x.constructor.name === 'Foo' // => true

Comparing against x.constructor.name has several drawbacks:

  • Asset minification will shorten all function names, breaking your comparison in the process. You can disable this in your minfier, costing you about 5% file size post-gzip.
  • It does not throw an error if you change the class name and forget to change the string

Duck typing

To check whether an object contains a property ("responds to a method" in Ruby lingo), use the in operator. It checks whether a property exists in the object or its prototype chain:

let parent = { parentKey: 1 }
let child = Object.create(parent)
child.childKey = 2

'childKey' in child  // => true
'parentKey' in child // => true
'otherKey' in child  // => false

Note that there is a difference between a property that is missing from an object, or a property being set to undefined:

let object = { foo: undefined }

object.foo      // => undefined
object.bar      // => undefined

'foo' in object // => true
'bar' in object // => false

NaN

Confusingly, typeof NaN returns "number". There is a built-in function isNaN(), but it also returns true for every other non-number value.

The appropriate check is this:

x = NaN
typeof x === 'number' && isNaN(x) // => true

null

typeof null returns "object", so it is not very helpful here.

The appropriate check is to simply compare it with another null:

x = null
x === null // => true

undefined

To check if a name is assigned the undefined value, compare it with another undefined:

x = undefined
x === undefined // => true

This will crash if x was never declared as a name in the current scope:

// y was never declared
y === undefined // Uncaught ReferenceError: y is not defined

When you want to check the value of a variable, crashing is exactly what you want.

If you really want to check if y is undeclared in the current scope (as opposed to being assigned the undefined value), you can use this:

// y was never declared
typeof y === 'undefined' // => true
Henning Koch
Last edit
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra dev (2017-10-02 07:17)