An Array By Any Other Type

An Array By Any Other Type

Type Checking Arrays in JavaScript

Featured on Hashnode

2021 is the twentieth anniversary of Douglas Crockford declaring JavaScript as the world's most misunderstood programming language. JavaScript is a complex language, with many follies; however, an under-represented subject in his article is type checking. Type checking is a known pain-point in JavaScript.

The Blunders of typeof

console.log(typeof null); // -> object

typeof null is a known design error1 that has existed too long to fix.

console.log(typeof []); // -> object

Is this another design error? No. Though this does break the rule of least astonishment, and suggestions have been made to change it, it isn’t considered a design error.

Why is the type of an array an object?

JavaScript consists of two sets of types: primitive values and objects.

What are Primitive Values?

A primitive value can be defined as anything which is not an object, is immutable, and has no methods2. These are: string, number, bigint, boolean, undefined, symbol, and null.

What are Objects?

Paraphrasing from JavaScript.info, an object is a keyed collection of data.

What are Arrays?

Arrays extend objects and are ordered collections of data; the key in our collection is an index. Arrays exist purely for convenience, providing implicit ordering and a flurry of helper methods.

Let's go back to the section title: Why is the type of an array an object?

It's defined to — ES5.1 and previous lists a type table stating that if it's an Object and does not implement the internal property [[Call]] the typeof will resolve to Object.

Pre-ECMAScript 2015

Before the introduction of Array.isArray there were two common methods3 of checking if a variable was indeed an array.

instanceof operator / constructor property:

let arr = [];
console.log(arr instanceof Array); // true
// likewise
arr = []; 
console.log(arr.constructor === Array); // true

There are a few nuances which make the above examples less than ideal.

  1. instanceof is easy to trick
  2. Globals don't extend beyond frames4

It Looks like an Array, and Quacks like an Array (Duck Typing)

// Prototype.js (v. 1.6.0.3):

function isArray(object) {
  return object != null && typeof object === "object" &&
    'splice' in object && 'join' in object; 
}

The above example of duck typing is also easy to trick. Any object with a key of splice and join will return true.

Object.prototype.toString - Duck Typing through internal properties.

Note: The current ECMAScript definition for Object.prototype.toString explicitly uses an isArray check.

A suggested patch in dojotoolkit's caveats provides an alternative for its internal use of instanceof5, introducing Object.prototype.toString:

console.log(Object.prototype.toString.call('')); // -> [object String]
console.log(Object.prototype.toString.call(1)); //-> [object Number]
console.log(Object.prototype.toString.call([])); // -> [object Array]

How? Up to ES5.1 every object defined internal semantics including an internal [[class]] property. Object.prototype.toString references the property.

This makes the implementation of isArray a string comparison against the return value.

function isArray(object) {
  return Object.prototype.toString.call(object) === '[object Array]';
}

Typing and the future

Since ES5 Array.isArray has alleviated our woes of typing arrays so much that there are talks of further implementations of the Type.isType style6.

Node has a util.types API which avoids the changeability of JavaScript, with the overhead of calling into C++.


  1. ^1 Design Errors is a heading mentioned in the article.
  2. ^2 Primitive values have wrapper objects that provide the functionality we're use to seeing in JavaScript.
  3. ^3 In the non-computer-science definition, i.e. ways of accomplishing something.
  4. ^4 I'm sure this is one of the reasons that Crockford considers JavaScript the world's most misunderstood language.
  5. ^5 It also references the aforementioned instanceof frames problem.
  6. ^6 The implementation of some built-in constructors have isType methods: e.g. Buffer's Buffer.isBuffer method.

Acknowledgements

Photo thanks to Daniel Salgado on Unsplash.

This article was inspired by a comment from Kyle Simpson on LinkedIn (must be logged in to see comment).

Some of this article was adapted from instanceof Considered Harmful by kangax.

A majority of this article references ES5.1 as it's more relevant to JavaScript's pre-isArray implementation.

Edits:

10/18/2021: update article url for instanceof Considered Harmful from previous archive.org link

10/22/2021: various grammatical, punctuation, and spelling fixes. No content changes.