Glad Chinda Full-stack web developer learning new hacks one day at a time. Web technology enthusiast. Hacking stuffs @theflutterwave.

JavaScript typeof: Understanding type-checking in JavaScript

6 min read 1958

JavaScript typeof

A very important aspect of every programming language is its type system and data types. For a strictly typed programming language like Java, variables are defined to be of a particular type, constraining the variable to only contain values of that type.

JavaScript, however, is a dynamically typed language, although some extensions exist that support strict typing, such as TypeScript.

With JavaScript, it is possible to have a variable that started off as containing a string, and much later in its lifecycle, has become a reference to an object. There are even times when the JavaScript engine implicitly coerces the type of a value during script execution. Type-checking is very critical to writing predictable JavaScript programs.

JavaScript has a pretty basic typeof operator for the purpose of type-checking.

However, you will notice that using this operator could be misleading, as we will discuss in this article.

JavaScript data types

Before looking at type-checking with typeof, it is important to have a glance at the JavaScript data types. Although this article does not go into details about the JavaScript data types, you can glean a thing or two as you progress.

Prior to ES6, JavaScript had six data types. In the ES6 specification, the Symbol type was added. Here is a list of all the types:

  1. String
  2. Number
  3. Boolean (the values true and false)
  4. null (the value null)
  5. undefined (the value undefined)
  6. Symbol
  7. Object

The first six data types are referred to as primitive types. Every other data type besides these first six is an object and may be referred to as a reference type. An object type is simply a collection of properties in the form of name and value pairs.

Notice from the list that null and undefined are primitive JavaScript data types, each being a data type containing just one value.

You may begin to wonder: Wwhat about arrays, functions, regular expressions, etc? They are all special kinds of objects.

  • An array is a special kind of object that is an ordered collection of numbered values with special syntax and characteristics that makes working with it different from with regular objects.
  • A function is a special kind of object that has an executable script block associated with it. The script block is executed by invoking the function. It also has a special syntax and characteristics that makes it different from other regular objects.

JavaScript has several object class constructors for creating other kinds of objects such as:

  • Date — for creating date objects
  • RegExp — for creating regular expressions
  • Error — for creating JavaScript errors

Type-checking using typeof

Syntax

The typeof operator in JavaScript is a unary operator(takes only one operand) that evaluates to a string indicating the type of its operand. Just like other unary operators, it is placed before its operand separated by a space:

typeof 53; // "number"

However, there is an alternative syntax that allows you to use typeof like a function invocation by wrapping its operand in parentheses. This is very useful for type-checking the value returned from JavaScript expressions:

typeof(typeof 53); // "string"

Error safety

Prior to ES6, the typeof operator always returns a string irrespective of the operand it is used on.

For undeclared identifiers, typeof will return “undefined” instead of throwing a ReferenceError.

console.log(undeclaredVariable === undefined); // ReferenceError
console.log(typeof undeclaredVariable === 'undefined'); // tru

However, in ES6, block-scoped variables declared using the let or const keywords will still throw a ReferenceError if they are used with the typeof operator before they are initialized. This is because:

Block-scoped variables remain in the temporal dead zone until they are initialized:

// Before block-scoped identifier: typeof => ReferenceError

console.log(typeof tdzVariable === 'undefined'); // ReferenceError

const tdzVariable = 'I am initialized.';

Type-checks

The following code snippet shows type-checks for common values using the typeof operator:

console.log(typeof ""); // "string"
console.log(typeof "hello"); // "string"
console.log(typeof String("hello")); // "string"
console.log(typeof new String("hello")); // "object"

console.log(typeof 0); // "number"
console.log(typeof -0); // "number"
console.log(typeof 0xff); // "number"
console.log(typeof -3.142); // "number"
console.log(typeof Infinity); // "number"
console.log(typeof -Infinity); // "number"
console.log(typeof NaN); // "number"
console.log(typeof Number(53)); // "number"
console.log(typeof new Number(53)); // "object"

console.log(typeof true); // "boolean"
console.log(typeof false); // "boolean"
console.log(typeof new Boolean(true)); // "object"

console.log(typeof undefined); // "undefined"

console.log(typeof null); // "object"

console.log(typeof Symbol()); // "symbol"

console.log(typeof []); // "object"
console.log(typeof Array(5)); // "object"

console.log(typeof function() {}); // "function"
console.log(typeof new Function); // "function"

console.log(typeof new Date); // "object"

console.log(typeof /^(.+)$/); // "object"
console.log(typeof new RegExp("^(.+)$")); // "object"

console.log(typeof {}); // "object"
console.log(typeof new Object); // "object"

Notice that all object type constructor functions, when instantiated with the new keyword will always have a type of “object”. The only exception to this is the Function constructor.

Here is a simple summary of the type-check results:

value typeof
undefined "undefined"
null "object"
true or false "boolean"
all numbers or NaN "number"
all strings "string"
all symbols "symbol"
all functions "function"
all arrays "object"
native objects "object"
host objects dependent on implementation
other objects "object"

Better type-checking

The type-check results from the previous section indicate that some values will require additional checks to further distinguish them. For example: null and [] will both be of “object” type when type-check is done using the typeof operator.

The additional checks on the value can be done by leveraging on some other characteristics:

  • Using the instanceof operator
  • Checking the constructor property of the object
  • Checking the object class using the toString() method of the object

Checking for null

Using the typeof operator to check for a “null” value does no good, as you have already seen. The best way to check for a “null” value is to do a strict equality comparison of the value against the null keyword as shown in the following code snippet.

function isNull(value) {
  return value === null;
}

The use of the strict equality operator(===) is very important here. The following code snippet illustrates this importance using the undefined value:

console.log(undefined == null); // true
console.log(undefined === null); // false

Checking for NaN

NaN is a special value received when arithmetic operations result in values that are undefined cannot be represented. For example: (0 / 0) => NaN. Also, when an attempt is made to convert a non-numeric value that has no primitive number representation to a number, NaN is the result.

Any arithmetic operation involving NaN will always evaluate to NaN.

If you really want to use a value for any form of arithmetic operation then you want to be sure that the value is not NaN.

Using the typeof operator to check for NaN value returns “number”. To check for NaN value, you can use the global isNaN() function, or preferably the Number.isNaN() function added in ES6:

console.log(isNaN(NaN)); // true
console.log(isNaN(null)); // false
console.log(isNaN(undefined)); // true
console.log(isNaN(Infinity)); // false

console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(null)); // false
console.log(Number.isNaN(undefined)); // false
console.log(Number.isNaN(Infinity)); // false

The NaN value has a very special characteristic. It is the only JavaScript value that is never equal to any other value by comparison, including itself:

var x = NaN;

console.log(x == NaN); // false
console.log(x === NaN); // false

You can check for NaN as follows:

function isNan(value) {
  return value !== value;
}

The above function is very similar to the implementation of Number.isNaN() added in ES6 and hence can be used as a polyfill for non-ES6 environments as follows:

Number.isNaN = Number.isNaN || (function(value) {
  return value !== value;
})

Finally, you can leverage on the Object.is() function added in ES6 to test if a value is NaN. The Object.is() function checks if two values are the same value:

function isNan(value) {
  return Object.is(value, Number.NaN);
}

Checking for arrays

Using typeof to check for an array will return “object”. There are several ways to better check for an array as shown in this code snippet:

// METHOD 1: constructor property
// Not reliable
function isArray(value) {
  return typeof value == 'object' && value.constructor === Array;
}

// METHOD 2: instanceof
// Not reliable since an object's prototype can be changed
// Unexpected results within frames
function isArray(value) {
  return value instanceof Array;
}

// METHOD 3: Object.prototype.toString()
// Better option and very similar to ES6 Array.isArray()
function isArray(value) {
  return Object.prototype.toString.call(value) === '[object Array]';
}

// METHOD 4: ES6 Array.isArray()
function isArray(value) {
  return Array.isArray(value);
}

Generic type-checking

As seen with arrays, the Object.prototype.toString() method can be very useful for checking the object type of any JavaScript value. When it is invoked on a value using call() or apply(), it returns the object type in the format: [object Type], where Type is the object type.

Consider the following code snippet:

function type(value) {
  var regex = /^[object (S+?)]$/;
  var matches = Object.prototype.toString.call(value).match(regex) || [];
  
  return (matches[1] || 'undefined').toLowerCase();
}

The following code snippet shows results of type-checking using the just created type() function:

console.log(type('')); // "string"
console.log(type('hello')); // "string"
console.log(type(String('hello'))); // "string"
console.log(type(new String('hello'))); // "string"

console.log(type(0)); // "number"
console.log(type(-0)); // "number"
console.log(type(0xff)); // "number"
console.log(type(-3.142)); // "number"
console.log(type(Infinity)); // "number"
console.log(type(-Infinity)); // "number"
console.log(type(NaN)); // "number"
console.log(type(Number(53))); // "number"
console.log(type(new Number(53))); // "number"

console.log(type(true)); // "boolean"
console.log(type(false)); // "boolean"
console.log(type(new Boolean(true))); // "boolean"

console.log(type(undefined)); // "undefined"

console.log(type(null)); // "null"

console.log(type(Symbol())); // "symbol"
console.log(type(Symbol.species)); // "symbol"

console.log(type([])); // "array"
console.log(type(Array(5))); // "array"

console.log((function() { return type(arguments) })()); // "arguments"

console.log(type(function() {})); // "function"
console.log(type(new Function)); // "function"

console.log(type(class {})); // "function"

console.log(type({})); // "object"
console.log(type(new Object)); // "object"

console.log(type(/^(.+)$/)); // "regexp"
console.log(type(new RegExp("^(.+)$"))); // "regexp"

console.log(type(new Date)); // "date"
console.log(type(new Set)); // "set"
console.log(type(new Map)); // "map"
console.log(type(new WeakSet)); // "weakset"
console.log(type(new WeakMap)); // "weakmap"

Bonus fact: Everything is not an object

It is very possible that at one point or the other, you may have come across this statement:

“Everything in JavaScript is an object.” — (False)

This could be very misleading and as a matter of fact, it is not true. Everything in JavaScript is not an object. Primitives are not objects.

You may begin to wonder — why then can we do the following kinds of operations on primitives if they are not objects?

  • (“Hello World!”).length — getting length property of the string
  • (“Another String”)[8] — getting the character of the string at index 8
  • (53.12345).toFixed(2) — calling Number.prototype.toFixed() method on the number

The reason why we can achieve these with primitives is because the JavaScript engine implicitly creates a corresponding wrapper object for the primitive and invokes the method or accesses the property on it.

When the value has been returned, the wrapper object is discarded and removed from memory. For the operations listed earlier, the JavaScript engine implicitly does the following:

// wrapper object: new String("Hello World!")
(new String("Hello World!")).toLowerCase();

// wrapper object: new String("Another String")
(new String("Another String"))[8];

// wrapper object: new Number(53.12345)
(new Number(53.12345)).toFixed(2);

Conclusion

In this article, you have been taken through a pinch of the JavaScript type system and its data types, and how type-checking can be performed using the typeof operator.

You also saw how misleading type-checking can be, using the typeof operator. And finally, you saw several ways of implementing predictable type-checking for some data types.

If you are interested in getting some additional information about the JavaScript typeof operator, you can refer to this article.

Enjoy coding…

Plug: , a DVR for web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Glad Chinda Full-stack web developer learning new hacks one day at a time. Web technology enthusiast. Hacking stuffs @theflutterwave.

Leave a Reply