What are Array‑Like Objects in JavaScript?

Abstract image used to represent What are Array‑Like Objects in JavaScript?
Image by Ash Edmonds.

In the world of frontend development and JavaScript, we developers often encounter structures that resemble arrays, yet aren't true arrays. These are called "arraylike objects", and understanding these unique constructs is crucial for effective JavaScript programming.


What are Array‑Like Objects?

An arraylike object exposes a length and indexed values, but it is not necessarily an Array. It does not inherit the full set of methods from Array.prototype, which is a prototype object, not a constructor. Some arraylike types provide their own methods: for example, modern NodeList objects have forEach().

Common Examples in JavaScript

  • NodeList: Returned by methods like document.querySelectorAll.
  • arguments: An object accessible within functions representing the arguments passed to that function.

Array‑Like Objects in Other Languages

As you might imagine, the concept of arraylike objects isn't unique to JavaScript. Many programming languages have similar concepts, where structures behave like arrays but aren't arrays in the strictest sense. For example, in Python, range() generates a sequence that is arraylike.


Working with Array‑Like Objects in JavaScript

Example: NodeList

When you use document.querySelectorAll, it returns a NodeList:

const divs = document.querySelectorAll('div');
console.log(divs.length);  //=> a number that represents the number of div elements within the document

Here, divs is a NodeList, which is an arraylike object, meaning you can access elements with their index (e.g., divs[0]), and you can read its length via .length. Relatively recent updates to the DOM standard even allow NodeList to be directly iterated over using forEach(). This wasn't always the case.

A NodeList does not provide every array method. Convert it with Array.from() or, where it is iterable, array spread if you want a normal array. Conversion is not always required: generic methods such as Array.prototype.map.call(divs, callback) can operate on its indexed values and length directly.

arguments and the RestParameter Alternative

An ordinary function has an arraylike arguments object containing the values passed to it. Arrow functions do not have their own arguments. For new code, I usually prefer rest parameters: they gather the values into a real array from the start. This TypeScript example uses that alternative:

const sum = (...args: number[]): number =>
  args.reduce((sum, current) => sum + current, 0);

console.log(sum(1, 2, 3));  //=> 6

Here, ...args creates a real array, so reduce() is available directly. It illustrates the restparameter alternative, rather than an arguments object.


Converting Array‑Like Objects to Arrays

Unsurprisingly, one of the common tasks when dealing with arraylike objects in JavaScript is converting them into 'actual' arrays, thus allowing us access to use all of the array methods on our data (like map, filter, reduce, etc.) which might otherwise be unavailable on this type of structure.

Array.from() accepts an iterable or an arraylike object. Array spread requires an iterable; having indexed properties and a length alone is not enough.

Using Array.from()

Array.from() creates a new, shallowcopied Array instance from an arraylike or iterable object. It's a straightforward and readable way to perform the conversion.

A Set is an iterable object that can be converted to an array using Array.from(). This is useful when you need array functionalities on a Set, for example:

const aSet = new Set([1, 2, 3, 4, 5]);
const anArray = Array.from(aSet);

console.log(anArray);  //=> [1, 2, 3, 4, 5]

Using the Spread Operator

Array spread reads an iterable's iterator. For example, const anArray = [...aSet]; works for a Set. But Array.from({ 0: "a", length: 1 }) returns ["a"], whilst spreading that plain object throws a TypeError because it has no iterator.

However, suppose you have two NodeList objects (which are arraylike), and you want to merge them into a single array. This can also be done efficiently using the spread operator, combining them and converting them at the same time:

const divs = document.querySelectorAll('div');
const paragraphs = document.querySelectorAll('p');
const mergedArray = [...divs, ...paragraphs];

console.log(mergedArray);  //=> Array of div and p elements

When to Use Which Method

Both approaches are useful, but first check the input. Array.from() handles plain arraylike objects as well as iterables; array spread handles iterables. Then consider readability and whether you also need a mapping step.

Readability

It may come down to personal preference, but I feel that Array.from() can be more readable, especially when you have relative juniors working on your codebase.

Performance

Both approaches allocate a new array. Performance depends on the input, engine and work performed during conversion; there is no general winner merely because Array.from() is a dedicated method. Measure if this conversion is significant in the application.

Additional Features of Array.from()

This is probably the big one: Array.from() can take a map function as its second argument, allowing for the transformation of elements during conversion. For example:

const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const anArray = Array.from(arrayLike, (x) => x.toUpperCase());

console.log(anArray);  //=> ['A', 'B', 'C']

Challenges with Array‑Like Objects

There are a few aspects of arraylike objects that can present as challenges to developers, particularly those who are a little less experienced:

  • Confusion:

    They look like arrays but don't behave entirely like arrays, leading to confusion, especially for beginners.
  • Available methods: check the object's own API. Convert to an array when that helps, or borrow a compatible generic array method when conversion is unnecessary.
  • Performance:

    Converting arraylike objects to arrays can have performance implications, especially with large datasets.

The Wrap‑up

Arraylike objects in JavaScript are a unique feature that, whilst powerful, can introduce complexity. Understanding their nature, and knowing how to convert them to arrays when necessary is a key skill in JavaScript development.


Planning a platform change?

I help teams make difficult platform work clearer, from architecture decisions and migrations to launch recovery, performance, and search visibility.