Arrays are Such a Useful Data Structure in JavaScript
Back when I began my new career as a web developer, the fact that I could loop through an array of objects in JavaScript was very cool.
const greetings = ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao'];
sayHello = (greeting) => {
for(let i = 0; i < greeting.length; i++){
console.log(greeting[i]);
}
};
sayHello(greetings);
// Hello
// Guten tag
// Salaam
// Hola
// Ciao
The classic
for
loop will always have its place, but there’s a world of other array methods just waiting to be utilized in JavaScript. The right tool for the right job is what I’m constantly trying to keep in mind when solving problems.
Now, having worked at it for almost 2 years, I’ve learned a heck of a lot more, including some much more sophisticated methods for manipulating arrays, which I’d like to share today.
One more thing I’d like to add before diving in, is that, in general, it tends to be a better practice not to alter existing arrays or objects. If you mutate the original item, you run the risk of unforeseen side effects: wherever else in the code that array or object is referenced, the mutated result could be used instead. This can make finding the root cause of a bug caused by the mutated code more difficult than it needs to be, to say the least.
Therefore, the methods I cover are known as pure functions, because instead of changing the original array, they instead make a copy and make the changes to that copy and return it to the scope outside of the method as a new array. Just something to keep in mind as you read through this article.
Map
The first method that will come in extremely handy is Array.map()
. The map method takes in an array, and creates a new array with the results of calling a function for every element in that array. It sounds a little complicated but it’s not too bad once you see some examples.
Here’s Array.map() being used on an array of strings:
const greetings = ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao'];
const sayHelloWorld = (hellos) => hellos.map(hello => {
return `${hello} world`;
});
console.log(greetings);
// ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao']
console.log(sayHelloWorld(greetings));
/* [
'Hello world',
'Guten tag world',
'Salaam world',
'Hola world',
'Ciao world'
] */
In this example, the map
loops over each element in the greetings array and produce a new array called sayHelloWorld
which has each greeting plus the word ‘world’ added to the end. But the original greetings
array remains unchanged, as you can see by the console.log(greetings)
line.
It can also be applied to complex arrays of objects, not just simple arrays.
And here is Array.map() creating a new array of objects:
const greetingsByCountry = [{language: "English", greeting: "Hello"},
{language: "German", greeting: "Guten tag"},
{language: "Arabic", greeting: "Salaam"},
{language: "Spanish", greeting: "Hola"},
{language: "Italian", greeting: "Ciao"}]
const helloByCountry = (hellos) => hellos.map(obj => {
let newObj = {};
newObj[obj.language] = obj.greeting;
return newObj;
});
console.log(greetingsByCountry);
/* [
{ language: 'English', greeting: 'Hello' },
{ language: 'German', greeting: 'Guten tag' },
{ language: 'Arabic', greeting: 'Salaam' },
{ language: 'Spanish', greeting: 'Hola' },
{ language: 'Italian', greeting: 'Ciao' }
] */
// helloByCountry is the new array of reformatted objects
console.log(helloByCountry);
/* [
{ English: 'Hello' },
{ German: 'Guten tag' },
{ Arabic: 'Salaam' },
{ Spanish: 'Hola' },
{ Italian: 'Ciao' }
] */
This array of objects greetingsByCountry
is used by map
to create a new array of objects where the key is language
and the value is greeting
. The original array of objects remains unchanged though. map
is very powerful and useful, and you'll find yourself reaching for it often.
Filter
Array.filter()
is another useful method. It works by creating a new array from the original of all the elements in that array that pass a test implemented by the provided function. Let’s take a look at an example of this.
Check out how Array.filter() works:
const greetings = ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao'];
const filteredGreetings = filterParam => {
return greetings.filter((hello) =>
hello.toLowerCase().indexOf(filterParam.toLowerCase()) > -1
)
};
// greetings is unchanged
console.log(greetings);
// ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao']
// filteredGreetings is a list of strings that contain the letter 'a'
console.log(filteredGreetings('a'));
// ['Guten tag', 'Salaam', 'Hola', 'Ciao']
The function filteredGreetings
takes in a query parameter (the string of "a"
in this case), and checks through the array of greetings
to see if that letter exists in these words. If it does, it returns the first index in the string in which that letter exists, and adds the word to the new array filteredGreeting
, and if it's not present in the string, the function returns -1, and it isn't added to the array.
Reduce
The Array.reduce()
method is powerful: it can be used in a variety of ways ranging from simple to very complex, significantly cutting down on the amount of code needed to achieve results. In essence, reduce
takes in an array, applies a function against something called an "accumulator" (more on that shortly) and each element in the array, and reduces it down to a single value.
Array.reduce()
can also accept a second, optional, argument called an "initial value", which is simply the first value to use for the first callback of the function. If it’s not supplied, the first value in the array will be used instead.
Now an accumulator, as the name suggests, accumulates the value returned by the function each time the function’s callback is invoked. Makes sense, right? If the answer is "no", it’s totally fine, read on for a few examples to help clear things up.
Below is a simple example adding up numbers in an array using Array.reduce().
const prices = [5.15, 7.55, 3.82, 10.11, 6.79];
const sum = (nums) => nums.reduce((acc, currentVal) => {
return acc + currentVal;
});
const sum2 = (nums) => nums.reduce((acc, currentVal, currentIndex) => {
return acc + currentVal;
}, 10);
// the array of prices is still intact
console.log(prices);
// [5.15, 7.55, 3.82, 10.11, 6.79]
// sum is its own value
console.log(sum(prices));
// 33.42
// sum2 is its own value
console.log(sum2(prices));
// 43.42
Two examples using Array.reduce()
on an array of prices.
Both functions take in the same array of numbers and run the same function (adding the accumulator to the current value being iterated over), but the second reduce
function also takes in a second parameter of an initial value.
For the sum
function, the array of nums
is taken in, and since no initial value is provided as a second argument to reduce
, the function begins at the index default of 1 (if not specified), and it iterates over each value adding the current index value to the accumulated value with each loop.
With sum2
, an initial value val
is provided as a second parameter to reduce
so the function begins with that value as the first one passed to the callback, and then continues with all the other numbers in the array afterwards. This means that before the internal function even runs, the sum2’
s value begins at 10 then loops through the rest of the array after that.
In the end, the prices
array remains unchanged, and both sum
and sum2
are their own individual values (numbers), as well.
Ready for another example? This one involves super heroes just for fun.
This second Array.reduce() example counts the number of times the various Avenger super hero names show up in the array.
const superHeroes = ['Black Widow', 'Iron Man', 'Hawk Eye', 'Hulk',
'Thor', 'Iron Man', 'Thor', 'War Machine', 'Captain America'];
const heroCount = heroes => heroes.reduce((allHeroes, hero) => {
if(hero in allHeroes) {
allHeroes[hero]++;
} else {
allHeroes[hero] = 1;
}
return allHeroes;
}, {});
// the array of Avengers is intact
console.log(superHeroes);
/* [
'Black Widow',
'Iron Man',
'Hawk Eye',
'Hulk',
'Thor',
'Iron Man',
'Thor',
'War Machine',
'Captain America'
] */
// the number of times each Avenger is named is a rand new object
console.log(heroCount(superHeroes));
/* {
'Black Widow': 1,
'Iron Man': 2,
'Hawk Eye': 1,
Hulk: 1,
Thor: 2,
'War Machine': 1,
'Captain America': 1
} */
In the above example, the superHeroes
array is iterated over and if a hero is not included in the empty object passed as the second param to the reduce function, it creates an object with the key as that hero's name and the count set to 1. If the hero's name is already present in the object, it increments the number of times that name is seen in the string until the whole array's been accounted for.
This is another example of how reduce
can be used to count the number of times a particular word is referenced in an array. The same kind of logic could also be used on a string, by simply splitting the string apart on spaces and then reducing through the resulting array and tallying up the words.
ForEach
The Array.forEach()
method is a cleaner, more streamlined version of the OG for
loop, but it is not always the right solution. For instance, if you need to loop over an array only until you find something that matches whatever argument it is you’re trying to find and then break out of the loop, forEach()
is not the answer. forEach
will keep going until it has gone through every element in that array. Period. Regardless of what it finds or doesn’t find.
If you want to stop a loop early when some value is found, an array method like some
or every
or find
might better suit your needs.
Another big caveat of forEach
is that it does not return anything. A forEach
loop will always return a value of undefined
. Unlike map
or reduce
, forEach
is not chainable and does nothing more than run whatever function you've defined against each item in the array. A typical use case is to execute side effects at the end of a chain.
Here's an Array.forEach() example where we deduplicate a set of greetings.
const greetings = ['Hello', 'Guten tag', 'Salaam', 'Hello', 'Ciao', 'Hello'];
/* a new empty array to take in the modified strings */
const dedupedGreetings = new Set();
const sayHello = (hellos) => {
hellos.forEach((hello) => {
dedupedGreetings.add(hello);
}
};
// greetings is unchanged
console.log(greetings);
// ['Hello', 'Guten tag', 'Salaam', 'Hello', 'Ciao']
// the new array has the unique greetings
console.log([...dedupedGreetings]);
// ['Hello', 'Guten tag', 'Salaam', 'Ciao']
The sayHello
function loops over each string in the array and tried to add the hello
to the dedupedGreetings
Set. Since dedupedGreetings
is declared outside of the forEach
and we add values to it through our callback function, it works, but this is an example of a side effect. It’s not a perfect equivalent of a for
loop, but it can certainly be useful in the right scenario.
Other Useful Array Methods
If there is a situation where you just need to know if an array includes a value, at least once, Array.includes()
, Array.some()
and Array.every()
may be the methods you need.
Includes
Like the method name indicates, Array.includes()
determines whether an array contains some element and returns a true
or false
boolean.
If the array includes it more than one time, it won’t indicate it because it will stop iterating as soon as it finds the first value that evaluates to true
. includes
can also take a second parameter as an argument: a search index, which specifies where the search of the array should begin according to the index.
Fun fact:
includes
was named as such because MooTools had already added a method to the array prototype calledArray.contains
which works slightly differently than howincludes
works.To avoid breaking all the sites currently employing MooTools'
contains
method, Ecma International decided to rename their array method toincludes
instead.
Here’s a simple example of Array.includes() at work:
const greetings = ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao'];
const includesHello = (hellos) => {
return hellos.includes('Hello');
}
// greetings is unchanged
console.log(greetings);
// ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao']
// returns `true` because `Hello` is in the greetings array
includesHello(greetings);
// true
This function just loops over the array and returns true
if the string is contained anywhere within the array.
Some
Array.some()
is similar to includes
, but instead of just taking in an array, it takes in both an array and the value or function to evaluate the items in the array against.
some
will iterate through an array and return true
or false
based on if it finds what it’s looking for. Also like includes
, some
will end its iteration as soon as it finds a value that evaluates to true
.
Check out this example of Array.some():
const greetings = ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao'];
const someHello = (hellos, typeOfHello) => {
return hellos.some(hello => hello === typeOfHello);
}
// greetings is unchanged
console.log(greetings);
// ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao'];
// the string `Hola` is present in the greetings array, so this function returns true
someHello(greetings, 'Hola');
// true
As you can see, the function someHello
, takes two arguments: the array and the value the function is looking for (typeOfHello
). Since ‘Hola’
exists, the function returns true
. When you just need to know if a value is present at all in an array, some
might be the function you need.
Every
Array.every()
is like some
, only every element in the array must pass the test and evaluate to true
for the function to return true
at the end. It’s pretty self-explanatory.
Here’s an example of Array.every() in action.
const greetings = ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao'];
const everyHelloGreaterThanFour = (hellos) => {
return hellos.every(hello => hello.length >= 4);
}
const everyHelloGreaterThanSix = (hellos) => {
return hellos.every(hello => hello.length >= 6);
}
// greetings is unchanged
console.log(greetings)
// ['Hello', 'Guten tag', 'Salaam', 'Hola', 'Ciao'];
// each greeting string has a length of 4 or more chars
everyHelloGreaterThanFour(greetings);
// true
// each greeting string is not 6 or more chars
everyHelloGreaterThanSiz(greetings);
// false
The first function everyHelloGreaterThanFour
, checks the length of all the strings in the greetings
array and since they’re all greater than or equal to 4, it returns true
.
The second function everyHelloGreaterThanSix
, does the same check on the greetings
array, but with a length greater than or equal to 6, and it returns false
since not all of the strings are at least that length.
Sort
Array.sort()
is an incredibly useful method, but it is also tricky remembering how it sorts if a compare function is not supplied, which directs sort
on how to compare and arrange the data.
Left to its own devices, sort
will convert all elements to strings and compare each element according to its Unicode order (so although “Banana” comes before “cherry” as strings, in a numeric sort, 9 comes before 80, because numbers are converted to strings, “80” comes before “9” in Unicode order).
We fix this by passing simple functions to tell sort
how to do its job on the values in encounters.
In general though, this is a very powerful and useful method that I use regularly, I just tend to supply a comparator function (I've been bitten many times when I'm lazy and don't).
Note: This method does modify the original array, but you’re only sorting it, and not directly altering the data, so there’s less chance of this type of change negatively impacting other blocks of code.
It can be computationally expensive though, if you're trying to sort a big enough array of comples objects.
Here’s Array.sort() on an array of numbers and on an array of objects.
const nums = [86, 10, 47, 29, 6, 55, 110];
// we tell the nums array how to sort its contents from lowest to highest
nums.sort((a, b) => a - b);
// nums is sorted, but otherwise unchanged
console.log(nums);
// [6, 10, 29, 47, 55, 86, 110]
const greetingsByCountry = [{language: "English", greeting: "Hello"},
{language: "German", greeting: "Guten tag"},
{language: "Arabic", greeting: "Salaam"},
{language: "Spanish", greeting: "Hola"},
{language: "Italian", greeting: "Ciao"}]
greetingsByCountry.sort((a, b) => {
let langA = a.language.toUpperCase();
let langB = b.language.toUpperCase();
if(langA > langB){
return 1;
}
if(langA < langB){
return -1;
}
if(langA === langB){
return 0;
}
})
// now greetingsByCountry is sorted alphabetically by language
console.log(greetingsByCountry);
/* [
{ language: 'Arabic', greeting: 'Salaam' },
{ language: 'English', greeting: 'Hello' },
{ language: 'German', greeting: 'Guten tag' },
{ language: 'Italian', greeting: 'Ciao' },
{ language: 'Spanish', greeting: 'Hola' }
] */
The first function sorts the numbers array from low to high. The second function sorts the greetings by various countries by their language value, which first converts all the characters to uppercase, (lowercase would work too), to eliminate the differences in Unicode caused by upper or lower case letters.
Another nice thing to note is that many of these methods can be chained together, eliminating the need for extra variables, and making your code cleaner.
And that’s it. JavaScript arrays and their built in methods.
Conclusion
These are some of the methods I use most often when working with arrays in JavaScript. If you get good with these, you’ll be able manipulate code into doing exactly as you want in no time at all.
Thanks for reading! Check back in a few weeks — I’ll be writing more about JavaScript, React, IoT, or something else related to web development.
If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com
Further References & Resources
- MDN documentation for
Array.map()
- MDN documentation for
Array.filter()
- MDN documentation for
Array.reduce()
- MDN documentation for
Array.forEach()
- MDN documentation for
Array.includes()
- MDN documentation for
Array.some()
- MDN documentation for
Array.every()
- MDN documentation for
Array.sort()
Top comments (0)