TechEarl

Array.from and the Spread Operator: Converting NodeLists, Sets, and Arguments to Arrays

Convert a NodeList, Set, arguments object, or string into a real array with Array.from or spread. When each one works, and why Array.from's map callback still earns its place over [...x].

Ishan Karunaratne⏱️ 7 min readUpdated
Share thisCopied
Convert a NodeList, Set, arguments object, or string to a real JavaScript array with Array.from or the spread operator, and when each one applies.

To turn a NodeList, Set, arguments object, or string into a real array, use Array.from(x) or the spread operator [...x]:

javascript
const divs = Array.from(document.querySelectorAll('div')); // NodeList -> Array
const items = [...new Set([1, 2, 2, 3])];                   // Set -> Array, [1,2,3]
const chars = [...'hello'];                                 // String -> Array, ['h','e','l','l','o']

Both give you a genuine array with map, filter, reduce, and the rest of the array methods attached. The two are interchangeable most of the time, but they are not equivalent, and the difference is worth knowing before you reach for one out of habit.

Why you need this at all

A NodeList from querySelectorAll, the arguments object inside a function, an HTMLCollection, a Set, a Map, a string: none of these is an Array. They look close enough that people try .map() on a NodeList and get TypeError: nodeList.map is not a function. They are either array-like (numeric indexes plus a length, but no array methods) or iterable (they work in for...of and yield values one at a time). To get the array methods, you have to convert.

The old way was Array.prototype.slice.call(arguments), borrowing slice from the array prototype and calling it on the array-like. It works, but it reads like a workaround, because it is one. Array.from and spread replaced it years ago and are Baseline across every browser and Node version you will meet today.

Array.from vs spread: the one real difference

javascript
// Both work on anything iterable:
Array.from(new Set([1, 2, 3]));   // [1, 2, 3]
[...new Set([1, 2, 3])];          // [1, 2, 3]

Where they diverge:

  • Spread only works on iterables. [...x] needs x to implement the iterator protocol (arrays, strings, Sets, Maps, NodeLists, generators). Spread a plain array-like object and you get TypeError: x is not iterable.
  • Array.from handles both iterables and array-likes. It accepts anything with a numeric length, even an object that is not iterable at all:
javascript
const arrayLike = { 0: 'a', 1: 'b', length: 2 };

Array.from(arrayLike); // ['a', 'b']
[...arrayLike];        // TypeError: arrayLike is not iterable

So if you are handed a length-bearing object that is not iterable (some older DOM collections, some library return values), Array.from is the one that works.

The map callback is why Array.from still earns its place

The bigger reason to keep Array.from in your toolbox: it takes a map function as a second argument, applied to every value as the array is built. Spread cannot do this. MDN documents the callback as receiving the element and its index, exactly like Array.prototype.map.

javascript
// Convert AND transform in one pass:
const lengths = Array.from(document.querySelectorAll('p'), p => p.textContent.length);

That is one allocation instead of two. With spread you would write [...nodes].map(p => p.textContent.length), which builds an intermediate array and then maps it. For a NodeList of a few elements the difference is academic, but the single-pass form is cleaner and the intent is clearer.

The callback also unlocks the trick that has nothing to do with conversion at all. Pass an array-like with just a length, no values, and the callback fills in each slot by index:

javascript
Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]

Array.from({length: 5}) on its own gives you [undefined, undefined, undefined, undefined, undefined]. Add the mapper and you have generated a sequence from nothing. This is the idiomatic way to build a range of numbers, and it is exactly the pattern in fill an array with a number sequence. Spread has no equivalent: [...{length: 5}] throws, because the object is not iterable.

Drop the arguments object in modern code

The classic use of slice.call was the arguments object: array-like, not an array, and the only way (pre-ES2015) to get the variadic args of a function as something you could iterate. You can still convert it:

javascript
function sum() {
  return Array.from(arguments).reduce((a, b) => a + b, 0);
}

But you should not have to. Rest parameters give you a real array directly, with no conversion step:

javascript
function sum(...args) {
  return args.reduce((a, b) => a + b, 0);
}

args is already an Array. No arguments, no Array.from, no slice.call. Rest params also work in arrow functions (which have no arguments binding at all) and read far better at the call site. Reach for arguments only when maintaining old code; write ...args in anything new.

Quick reference

You haveIterable?[...x]Array.from(x)Notes
NodeList / arrayyesworksworkseither is fine
Set / Mapyesworksworksspread is terser
Stringyesworksworkssplits into code points
argumentsyesworksworksprefer ...args instead
{length: n} array-likenothrowsworksArray.from only
Need to map while converting-noworksArray.from(x, fn)

The rule of thumb: reach for [...x] when you just want a quick copy of something iterable, because it is the shorter read. Reach for Array.from when the source is a non-iterable array-like, or when you want to transform values in the same pass with the second-argument callback. Both produce a fresh, shallow copy, so neither mutates the original. Once you have an array, removing duplicates and the rest of the standard transforms are all available.

See also

Sources

Authoritative references this article was fact-checked against.

TagsArray.fromspread operatorNodeListJavaScript arraysiterablearray-likerest parameters

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

SSRFmap Cheat Sheet: Every Module and Flag I Actually Use

A field-tested SSRFmap reference: target capture, the real module list (readfiles, portscan, redis, fastcgi, mysql, smtp, axfr, aws, gce, alibaba, digitalocean, github, zabbix, postgres, docker, socksproxy, smbhash, tomcat, memcache, networkscan, custom), handler setup, cloud metadata workflows, and where Burp Repeater is still the better tool.