Posted about 2 years ago. Visible to the public. Repeats.

JavaScript: Testing the type of a value

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 or Unpoly's up.util 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:

Copy
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:

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

Your test should check both forms:

Copy
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:

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

Your test should check both forms:

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

Functions

Copy
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:

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

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

Copy
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 assign keys or values to an object.

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

Copy
(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:

Copy
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 returned by document.querySelectorAll().

Instances of a given class

Use instanceof to check if an object is an instance of the given class:

Copy
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:

Copy
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:

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

Comparing against x.constructor.name has several drawbacks:

  • It relies on Function.name, which you need to polyfill for Internet Explorer
  • 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

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:

Copy
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:

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

undefined

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

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

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

Copy
// 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:

Copy
// y was never declared typeof y === 'undefined' // => true

By refactoring problematic code and creating automated tests, makandra can vastly improve the maintainability of your Rails application.

Owner of this card:

Avatar
Henning Koch
Last edit:
over 1 year ago
by Florian Leinsinger
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more