If you have any questions regarding anything in this series please get in contact with me by using the methods listed at the bottom of this post and if you think I've got something wrong please create a pull request on GitHub to correct the information (link at the bottom of the article).
Method #1: Array.from()
Array.from() allows for the creation of a new array from an arrayLike or iterable object.
What this means is we can take something that is like an array (e.g. NodeList) and then create a full blown array from that arrayLike object with the new array then having access to the full Array.prototype.
Essentially this means we can take something that isn't an array and then create it into an array by using Array.from() so we gain access to the methods on the Array.prototype. But, before looking at any examples or the syntax of Array.from(), I want to make a quick side-note about why you won't find .from() on existing arrays.
Array.from() does not actually exist on a existing array's prototype but rather on the parent 'Array' prototype, this may seem confusing but it makes more sense when we look at some code:
[1,2,3,4,5].from() // Uncaught TypeError: [1,2,3,4,5].from is not a function Array.from('string') // ["s", "t", "r", "i", "n", "g"]
Looking at the first example, we are trying to create a new array using Array.from() from an existing array so it makes sense that this returns an error because why would you need to create an array from an array? 🤷♂
But, on the second example, we are converting a string into an array using Array.from() so it makes sense that in this case it doesn't return an error but instead returns us the new array.
Essentially, the reason why the .from() method doesn't exist on the existing array is because it has no use there, so instead it exists on the parent 'Array' type which is like the mother array that all over arrays are created off. As this mother array can't store values itself and is instead a template for child arrays to be created from, the .from() method can be called from the mother array to create new arrays.
Note the capital 'A' is important as it signifies we are referencing the Array type and not an actual created array. If you tried to call it using array.from() it would return an error saying 'array' is undefined because it would be looking for a array stored in a variable called 'array'.
Now, let's take a look at the syntax for using Array.from().
Officially, this method takes 3 parameters when it is called 2 of which are optional parameters.
Array.from(arrayLike [, mapFn [, thisArg]])
- arrayLike: This is the arrayLike object that we want to convert into a full blown away, this is the only mandatory parameter for this method.
- mapFn: This the first optional parameter and is a .map() function we can pass to the .from() method should we want to immediately map over the newly created array so the returned array from Array.from() is the mapped over array, doing this removes the need to create an intermediate array.
- thisArg: The final parameter and second optional parameter is the thisArg or the value that we wish to use as 'this' when executing the previously mentioned 'mapFn' function.
Finally, this method returns a new Array instance (based of the mother Array data type we mentioned earlier) but with the values we passed in as the arrayLike argument.
Here are some examples to showcase where this method may be helpful.
1. Array from a string
A simple use case for Array.from would be converting a string into an array.
Array.from('this is a string!'); // Output: ['t', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '!']
As you can see it loops over the entire string and splits it on each character and adds the character into it's own value within the array. This has the same result as calling '.split('')' on the original string which would yield the same output so for this reason using '.from()' like this has limited use cases that couldn't be fulfilled by '.split()' but it's good to know that this is possible.
2. Converting an arrayLike object into an Array
<ol> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ol>
document.querySelectorAll('li'); // Output: NodeList(5) [ li, li, li, li, li ]
As you can see we get returned a NodeList and not an Array which is what we might have expected to be returned.
Now, a NodeList has a few of the prototype methods that Array has but not all of them leading us to a common situation where we need to convert a NodeList into an Array which is a perfect use case for Array.from():
const nodes = document.querySelectorAll('li'); const nodeArray = Array.from(nodes);
Now we have a brand new array stored in the nodeArray variable which would allow us to call prototype methods not available on the NodeList object. For example, we can now call '.map()' on our new array:
nodeArray.map(node => parseInt(node.innerText) * 2); // Output: [2, 4, 6, 8, 10]
There is a full list of Array.prototype methods not available on arrayLike objects that are available on the Array.prototype so I'll save going over the differences for another post but just know for now if the method you're trying to use is throwing an error first make sure you are dealing with a proper array and not an arrayLike object.
Now, we've mentioned using '.map()' on an arrayLike object and how we need to convert it to a full array first, hopefully you were screaming at your screen saying why aren't using the callback function we mentioned in the syntax above. So let's take a look at that now.
3. Passing a .map callback function into the .from() method
If we are purely converting an arrayLike object for no other reason but to map over it and return a new array (we will cover .map() in more detail in it's own post but for now just know it returns a new array by looping over the passed in array and performing operations on it) then we should look at using the optional parameter on the Array.from() method we briefly mentioned above. Let's take a look at doing this now:
// using the previous examples nodeList const nodes = document.querySelectorAll('li'); // We now have a couple of ways we could perform the .map method on this arrayLike object. // 1. Store the converted array into a variable and then map over it. (What we did above). const nodeArray = Array.from(nodes); const mappedArray = nodes.map(node => parseInt(node.innerText) * 2); console.log(mappedArray); // output: [2, 4, 6, 8, 10] // 2. CHain the .map onto the Array.from method like so: const nodeArray = Array.from(nodes).map(node => parseInt(node.innerText) * 2); console.log(nodeArray); // output: [2, 4, 6, 8, 10] // 3. Use the callback function shown earlier. const nodeArray = Array.from(nodes, node => parseInt(node.innerText) * 2); console.log(nodeArray); // output: [2, 4, 6, 8, 10]
Now, you may be asking what is the difference between methods 2 and 3 and to be honest not a whole lot as far as the syntax goes. The main difference is the '.map()' is chained to the .from() in method 2 but passed in as an argument in method 3. But, other than this the code remains largely the same.
But, the real differences come about when we think about what is actually happening under the hood.
When we call Array.from() on an arrayLike object we are creating a new Array with all of the items included from the original arrayLike object, but what makes method 3 more appealing is how we go about mapping over the items and returning the new mapped array.
When we look at method 2, we can see that the original array created by Array.from() is created and then we call '.map()' on it which in turn creates another array with the new values which have been returned from the .map() method. This is the array we are interested in working with in this example.
However, when we look at method 3's code we have integrated the '.map()' into the Array.from() call using the callback function as the second argument. Now, this small change doesn't appear to be that important but in reality what it means is we don't create a intermediate array that has no purpose in the code leading to more efficient and cleaner code.
Now, to veer on the side of caution I would say I'd only use the 3rd method if you absolutely are sure that you won't need access to the original array again and are only going to use the returned array from the '.map()' method. If you think you might need the original array then I would steer towards using the 1st method for the flexibility it provides but you still need to be aware of the possibility that .from() can perform a .map() method inside it.
I hope you enjoyed this first post into looking at the Array.Prototype methods, I plan on realising these on a regular basis throughout the coming weeks and I would greatly appreciate it if you could share these articles with people you think may find this content helpful or interesting.
As always if you believe you have found any issues please use the GitHub link below to submit a pull request for the content to be changed and I will review it as soon as possible. If you have any questions please contact me via the links below.
#2 will be on: Array.isArray() and will be released on 28/05/20, see you then.😃