I strongly believe, and I don’t think there are many people who will tell you otherwise, that being an effective developer requires an openness to learning new concepts and techniques. This is true whether you are joining a new project and need to get up to speed with the technology being used, or perhaps you are just curious about something you may have read about in a magazine or on the internet somewhere. Every now and then though, I find that I learn something that stands out above everything else. Something that has a real impact on how I write code and do my job effectively.
Some of these things in the JavaScript world are some of the methods that are part of the Array prototype. These are quite often unknown to those getting to grips with the language, and in some cases even overlooked by those that have been using the language for some time. In no particular order these methods are Array.prototype.Map, Array.prototype.Filter, Array.prototype.Find, Array.prototype.forEach and finally Array.from.
These methods, with the exception of Array.from are defined on the JavaScript Array prototype, and because all Array instances in JavaScript inherit from this prototype, these methods are available to every single instance of Array you have in your code, ready to go whenever you are – without having to pull in any extra libraries! Happy days. There is one slight exception here though, Array.from is not defined on the Array prototype so you can’t use this on an array instance, more on this later though.
Arrays are so ubiquitous in most codebases that learning even some these methods and how to use them effectively really pays off in the long run. This post will aim to explain some of these methods and give you some examples on how you can get started using them in your own projects.
forEach
Array.prototype.forEach gives you a nice and easy-to-read way of iterating through each element you have in your array, essentially reducing the need of writing so many for loops. A simple example of this in use would be if we have an array of bears, or more specifically in this case, an array containing just the names of some bears.
If we needed to write a small program that will greet each bear we have in the array, there are a couple of ways we could approach this. One way of doing this would just be the tried and tested for loop as shown in the example below.
const bears = [ "Yogi" , "Baloo" , "Po" , "Winnie" , "Bungle" ]; function greet(bear) { console.log(“Hello there, ” + bear + ”!”); } for (let i = 0 ; i < bears.length; i++) { greet(bears[i]); } // Hello there, Yogi! // Hello there, Baloo! // Hello there, Po! // Hello there, Winnie! // Hello there, Bungle! |
While there is nothing really wrong with this code, given that it achieves exactly what we want it to, for loops can be little tricky to understand at just a glance. The syntax of the loop isn’t the most readable thing in the world and we can make this code more much more readable and friendly by getting rid of the scary looking for loop syntax. A quick way of doing this is by using the forEach method thats available on our array instance.
const bears = [ "Yogi" , "Baloo" , "Po" , "Winnie" , "Bungle" ]; function greet(bear) { console.log(`Hello there, ${bear}!`); } bears.forEach((bear) => { greet(bear); }); // Hello there, Yogi! // Hello there, Baloo! // Hello there, Po! // Hello there, Winnie! // Hello there, Bungle! |
As you can see, the output of our new program is exactly the same as the old one. One of the benefits of this new way of writing our new code, however, is that any developer or future maintainer can now immediately see that that something is happening for each bear in our bears array. In this case, the greet function is being called.
But what’s actually happening here? Well, without going into too much detail on this, the way that forEach works is by allowing us to provide a function that will be called once for each element in the array.
When our code executes, the array will be iterated, and then for each element in the array, the function we provided is called once (and only once), when the function is called it will receive the current element as the first argument and we can then use this as a reference to the currently iterated item, we can then pass this to our greet function.
This is quite nice because we now don’t have to write the loop boilerplate or worry that much about the logic of how the array is actually being iterated (to a degree anyway, but for the purposes of this post, accept it as some helpful black magic) so the code becomes a little easier to understand. However, it is possible to refactor this a little bit further, we can remove a few lines and make it even easier to read.
const bears = [ "Yogi" , "Baloo" , "Po" , "Winnie" , "Bungle" ]; function greet(bear) { console.log(`Hello there, ${bear}!`); } bears.forEach((bear) => greet(bear)); // Hello there, Yogi! // Hello there, Baloo! // Hello there, Po! // Hello there, Winnie! // Hello there, Bungle! |
Array.prototype.forEach can also be used to mutate the elements in the array that it is working on.
There are some gotchas to bear in mind though when it comes to forEach, one is that once you start a forEach loop, there is no way to stop the loop until it finishes executing. That is until it finishes iterating through every element in your array unless you throw an exception. But that is probably not the best idea, so if you need to stop the execution of the loop once it’s started, this method is not the one for you.
Another caveat is that the elements that will actually be processed by forEach (those in the array that are going to have the function we provide invoked on) are set before the invocation of the first function – this means that if you remove/add elements to the array as part of the function, then you run the risk of elements being skipped.
More information is available on MDN – https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
Map
Array.prototype.Map is another extremely useful method. Map is able to iterate through an array in a very similar fashion to forEach and execute a function for each element in the array, however unlike forEach (see above) map is able to return an array. This can be extremely useful in cases where you want to apply some sort of transformation function to your array but you don’t want to mutate your original data-structure.
A key difference between forEach and Map is that the function that you provide to Map, while still being executed once per element in the array, is able to return something, the value this returns is an element for a new array that’s being created. One of the key ideas here is that the array you are iterating over is not mutated, instead, you get a new array and the old array remains untouched.
Teddy Bears
For this example, we will be using the below array of bears. Each individual bear object has a name and type property, these properties are both strings.
const bears = [ { "name" : "Baloo" , "type" : "Brown" }, { "name" : "Yogi" , "type" : "Brown" }, { "name" : "Po" , "type" : "Panda" }, { "name" : "Bungle" , "type" : "Brown" } ]; |
We want to be able to turn our bears into Teddy bears, ready for sale on our new teddy bear website. We have an array of our raw materials (bears) and we want to use these to create our Teddies. This will require changing the type property of all the bears to “Teddy” and adding a new Price property, the price for all Teddy bears will start at £5.00. While it would be possible to achieve this goal using a for loop, this isn’t the most readable or declarative way of doing it. Instead what we can do is the below:
let teddyBears = bears.map((bear) => { return { "name" : bear.name, "type" : "Teddy" , "price" : "5.00" } }); |
As you can see we make use of the bears.map method, this will iterate through our array of bears, then for each bear in the array, the function we pass is executed. That function will return us a new object (a teddy bear). This new teddy bear will then be placed in our new teddyBears array. Also as we have access to the properties of the currently iterated bear, we can take the name property (or any others we may want) and put it into our new teddy bear object.
If we now add the below functions to log out our arrays
bears.forEach((bear) => { console.log(JSON.stringify(bear)); }) teddyBears.forEach((teddyBear) => { console.log(JSON.stringify(teddyBear)); }); //{"name":"Baloo","type":"Brown"} //{"name":"Yogi","type":"Brown"} //{"name":"Po","type":"Panda"} //{"name":"Bungle","type":"Brown"} //{"name":"Baloo","type":"Teddy","price":"5.00"} //{"name":"Yogi","type":"Teddy","price":"5.00"} //{"name":"Po","type":"Teddy","price":"5.00"} //{"name":"Bungle","type":"Teddy","price":"5.00"} |
By looking at this output we can see that our newly created array, teddyBears, has our created teddy bears in it. Each bear in teddyBears has been given a type of “Teddy” and a price of “5.00” as we expected. Each teddy bear also has a name property which matches each of our original bears, this is where we have taken the bear.name property in our function and assigned it to our teddy. As a bonus, we still have our original bears array which has been untouched by this process. We can refactor this code and make it even more declarative in style by creating a new “teddify” function. This new function essentially does the same thing as the logic that was previously contained within our map function but this way makes it a little bit easier to read and encapsulates our function a little more.
function teddify(bear) { return { "name" : bear.name, "type" : "Teddy" , "price" : "5.00" } } let teddyBears = bears.map((bear) => teddify(bear)); |
There’s actually one last thing we can do here to make this look a little nicer again (thanks to Jaakko Salonen for pointing this one out to me) – as our teddify function takes in a parameter and returns a value, it’s possible to just pass Map a reference to our teddify function like below. This means we can get rid of our lambda boilerplate and our results will be exactly the same.
let teddyBears = bears.map(teddify); |
Filter
Another extremely useful method to add to the list is Array.protoype.Filter. This a method that can filter out certain elements from your array. Or a little more accurately, it will return you a new array which is essentially a copy of your original array – but that just consists of the elements that match the filter criteria you provide.
If you’ve ever had to somehow filter elements from your array, you know already that the code can be quite complex and tricky to read when sticking to plain for old for loops. Filter makes this common task much easier, by allowing you to provide a function that in its most simple form, just returns a boolean – True if you want the element meets your filter criteria, and False if not. Using this you can write fairly complex filter functions, which are essentially criteria for the elements that make it to your new array.
For example, if we had an array with values [1, 2, 3] and we wanted to filter out all of the elements that were not equal to “1” we could make use of Filter and provide it a function that would simply evaluate whether or not the value is equal to one and then return the result of that evaluation.
let results = [ 1 , 2 , 3 ].filter((x) => { return x === 1 ; }); // [1] |
But what if we wanted to do the inverse? What if we wanted to get all of the elements in the array that did not equal 1? Well, in this case the answer is actually quite simple, to the point where its not actually immediately obvious (I know that personally, the first thing I did when I was getting to grips with this was to start searching through the docs for some sort of opposite method to filter) In most cases though, all we need to do is just negate the result of the expression we are already using like below:
return !(x === 1 ); |
Or, even better, we can just use the not equal operator !== .
let results = [ 1 , 2 , 3 ].filter((x) => { return x !== 1 ); }); // [2,3] |
And finally, let’s try some examples that are a little more complex – What about when we have objects in our array or maybe we even have multiple criteria that we would like to filter by?
const bears = [{ "Name" : "Yogi" , "Age" : "6" , "Group" : "Red" }, { "Name" : "Baloo" , "Age" : "5" , "Group" : "Blue" }, { "Name" : "Winnie" , "Age" : "3" , "Group" : "Red" }]; |
Using the array above, what would we do if we only wanted to gather up the bears that were younger than 5 ? – That’s fairly simple, we can just access the Age property on the current bear and check against it like below:
let results = bears.filter((bear) => { return bear.Age < 5 ; }); // [{"Name":"Winnie","Age":"3","Group":"Red"}] |
And what if we wanted to get bears aged 5 or older, that only belong to group Red? Again, not too difficult, all we need to do is specify an extra rule and make sure both conditions evaluate using the && operator.
let results = bears.filter((bear) => { return bear.Age >= 5 && bear.Group === "Red" ; }); // [{"Name":"Yogi","Age":6,"Group":"Red"}] |
Finally, what if we want to get all of our bears that are 5 or older and belong to either group Red or Blue?
let results = bears.filter((bear) => { return bear.Age >= 5 && ( bear.Group === "Red" || bear.Group === "Blue" ) ; }); // [{“Name”:”Yogi","Age":6,"Group":"Red"},{"Name":"Baloo","Age":5,"Group":"Blue"}] |
Find
Array.prototype.find was added to the language in ES6 (ES2015) – This means that it won’t work with all browsers natively, Its is possible however to still use this method in older browsers if you use a polyfill or transpiler such as Babel or TypeScript.
A tried and tested pollyfill for Array.prototype.find can be found on the MDN website.
Array.prototype.find is able to iterate through an array of elements and then return the first element in the array that matches a provided testing function.
Again, with the below examples we will use our array of bears as defined below:
const bears = [ { "name" : "Baloo" , "type" : "Brown" }, { "name" : "Yogi" , "type" : "Brown" }, { "name" : "Po" , "type" : "Panda" }, { "name" : "Bungle" , "type" : "Brown" } ]; |
A simple find in action:
let bear = bears.find((b) => { return b.type=== "Brown" ; }); console.log(bear); //{name: "Baloo", type: "Brown"} |
One thing to notice here is that unlike with Filter, Find only returns one element even when we have many in our array that match the criteria we are supplying in our test function.
One gotcha with this, and with many other methods here too, is to remember to always return the result of your test, if you forget this then it will not work, for example:
let bear = bears.find((b) => { b.type=== "Brown" ; }); // Will not work |
We can of course also make more complex tests by using Boolean operators
let bear = bears.find((b) => { return b.type === "Brown" && b.name === "Bungle" ; }); console.log(bear); //{"name": "Bungle", "type": "Brown"} |
But what if no element in our array matches our search criteria?
let bear = bears.find((b) => { return b.type === "Polar" && b.name === "Ice" ; }); console.log(bear); //undefined |
Well, in this case, the function will simply return undefined. So It’s important to keep this in mind when using this method and handle any cases which may give you an undefined result appropriately.
Array.from
Array.from is a helpful method that can be used to create a new instance of an Array from an “array-like” data structure or anything that implements the Iterable interface, things like Map/Set and some other Objects and even Strings can be used. Its important to note that this is a shallow copy of the source object.
Similarly to Array.prototype.find, Array.from was added to JavaScript in the ES6 (ES2015) specification, this means that older browsers won’t support this natively. Again, it is possible to use this if you use a polyfill or transpiler such as Babel or TypeScript.
A tried and tested pollyfill for Array.from is available on the MDN website.
The way that Array.from works is slightly different to the other methods we have talked about so far in this post, This is in part related to the way that the function is defined. If compared to other languages this is extremely similar to whats known as a Static method. I won’t go into too much detail about it here, but what I will say is that because the from function is assigned to the global Array object, and not to the prototype of that object the from function is not available to (inherited by) any instances of Array. So if we wanted to invoke this method we would need to use the below syntax:
Array.from() // Correct |
If we tried to do the below, however, we would generally receive a compiler error:
let x = [1,2,3]; x.from() // Wrong. // Uncaught TypeError: x.from is not a function. |
If you think about it though, this syntax makes sense given the context of what we expect the from method to actually do, which returns us a new instance of Array fromsomething else.
A simple example of how we can create an array from a string:
let foo = “Hello World”; let bar = Array.from(foo); console.log(bar); // (11) ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", “d"] |
Creating an array from a Map
let foo = new Map([[ "a" , 1 ], [ "b" , 2 ], [ "c" , 3 ]]); let bar = Array.from(foo); console.log(bar[ 0 ]); //(2) ["a", 1] console.log(bar[ 1 ]); //(2) ["b", 2] console.log(bar[ 2 ]); //(2) ["c", 3] |
Summary
There are many more methods available on Array that will dramatically improve the quality of your code (and your life!) once you know about them and learn how to use them effectively.
Also, we are only just scratching the surface here, there are many more ways to use these methods, and many of the above methods have additional arguments you can provide that will allow you to use extra functionality that we haven’t mentioned here.
Hopefully, this is just enough for you to start putting these methods to good use.