
Arrays (learn more)
In JavaScript, arrays are probably the most used data structure because we use them all the time and therefore, JavaScript has countless modern array methods. In this article, we will learn all about these tools and how to use them.
Simple Array Methods
Why arrays do actually have methods?
Well, remember that methods are simply functions that we called on objects. So, basically, they are functions attached to objects. So, if we have array methods, that means that arrays themselves are also objects and they get access to special built in methods that can essentially see as tools for arrays. Let's learn some very simple methods that we can use them on arrays.
It's very similar to the Slice() method available on Strings. With the Slice method, we can extract part of any array but without changing the original array. So essentially, we can take slice of an array.
For example,
let arr = ['a',"b","c","d","e"]
console.log(arr.slice())//[ 'a', 'b', 'c', 'd', 'e' ]
console.log(arr.slice(2))//[ 'c', 'd', 'e' ]
console.log(arr.slice(2,4))//[ 'c', 'd' ]
console.log(arr.slice(-2))//[ 'd', 'e' ]
console.log(arr.slice(1, -2))//[ 'b', 'c' ]
Key points,
- it accepts two parameters and they are actually optional
- startIndex(optional): index at which to start extraction and negative index starts extraction from the end of the array
- endIndex(optional): index at which to end extraction but not including the endIndex and negative index counts back from the end of the array
- if we don't specify any arguments in slice() method, then it will returns a shallow copy of the original array
The splice() method works in almost the same the way as slice() method, but the fundamental difference is that, it does actually change the original array so, it mutates that array. Using splice() method, we can removing or replacing existing elements or adding new elements.
let's see some examples,
let arr = ['a',"b","c","d","e"]
console.log(arr.splice(2))//[ 'c', 'd', 'e' ]
console.log(arr)//[ 'a', 'b' ]
As you can see that, all the remaining elements in our original array is the first two elements and now extracted elements are actually removed from the original array so, Splice deleted them
let arr = ['a',"b","c","d","e"]
console.log(arr.splice(-1))//[ 'e' ]
console.log(arr)//[ 'a', 'b', 'c', 'd' ]
Splice removed the last element from the original array
Syntax
splice(start)
splice(start, deleteCount)
splice(start, deleteCount, item1)
splice(start, deleteCount, item1, item2)
splice(start, deleteCount, item1, item2,..., itemN)
Parameters
- start: index at which to start changing the array
- deleteCount(optional): An Integer indicating the number of elements in the array to be removed from the start. for example, remove 2 elements from start index 1
let arr = ['a',"b","c","d","e"]
console.log(arr.splice(1,2))//[ 'b', 'c' ]
console.log(arr)//[ 'a', 'd', 'e' ]
- item1,...itemN(optional): The elements to be added to the array, beginning from start, for example, add and before 'c' without deleting any elements
let arr = ['a',"b","c","d","e"]
arr.splice(2,0,"j","k")
console.log(arr)//[ 'a', 'b', 'j','k', 'c', 'd','e' ]
NOTE: To splice the elements in an array without mutating the original array, use toSpliced() method
The reverse() method allows us to reverse the array, but it does actually mutate the original array
For example,
let arr = ['a',"b","c","d","e"]
console.log(arr.reverse())//[ 'e', 'd', 'c', 'b', 'a' ]
console.log(arr);//[ 'e', 'd', 'c', 'b', 'a' ]
NOTE: To reverse the elements in an array without mutating the original array, use toReversed() method
The concat() method allows us to merge two or more arrays. This method does not mutate/change the existing arrays, but instead returns a new array.
For example,
let arrOne = ['a',"b","c","d","e"]
let arrTwo = ['f',"g","h","i","j"]
console.log(arrOne.concat(arrTwo))//[ 'a', 'b', 'c', 'd','e', 'f', 'g', 'h','i', 'j' ]
NOTE: we can also contact two or more arrays using spread operator, for example [...arrOne,...arrTwo]. It also does not mutate any of the existing arrays
#join
The join() method allows us to join all of the elements in an array using the separator and returns a new string.
For example,
let arr = ['a',"b","c","d","e"]
console.log(arr.join("*"))//a*b*c*d*e
#push
The push() method allows us to add the specified elements to the end of an array and returns the new length of the array.
For example,
let arr = ['a',"b","c","d","e"]
console.log(arr.push("f"))//6
console.log(arr)//[ 'a', 'b', 'c', 'd', 'e', 'f' ]
console.log(arr.push("g","h","i"))//9
console.log(arr)//[ 'a', 'b', 'c', 'd', 'e', 'f','g', 'h', 'i' ]
#pop
The pop() method allows us to remove the last element from the array and returns the removed element.
For example,
let arr = ['a',"b","c","d","e"]
console.log(arr.pop())//e
console.log(arr)//[ 'a', 'b', 'c', 'd' ]
console.log(arr.pop())//d
console.log(arr)//[ 'a', 'b', 'c' ]
The shift() method allows us to remove the first element from the array and returns the removed element.
For example,
let arr = ['a',"b","c","d","e"]
console.log(arr.shift())//a
console.log(arr)//[ 'b', 'c', 'd', 'e' ]
console.log(arr.shift())//b
console.log(arr)//[ 'c', 'd', 'e' ]
The unshift() method allows us to add the specified elements to the beginning of an array and returns the new length of the array.
For example,
let arr = ['a',"b","c","d","e"]
console.log(arr.unshift('n'))//6
console.log(arr)//[ 'n', 'a', 'b', 'c', 'd', 'e' ]
console.log(arr.unshift('m'))//7
console.log(arr)//[ 'm, 'n', 'a', 'b', 'c', 'd', 'e' ]
#At
The At() method takes an integer value and returns the item at that index and this method allows us to use both positive and negative integer. if we use positive integer then it starts from the beginning of the array to find the element and if we use negative integer then it starts from the end of the array to find the element. for example,
let arr = ['a',"b","c","d","e"]
console.log(arr[0]);//a
console.log(arr.at(0));//a
console.log(arr[-1]);//undefined
console.log(arr.at(-1));//e
console.log(arr[arr.length - 1]);//e
as you can see that, At() method works exactly our traditional bracket notation([]). However, there is one particularly of the At() method, which make it quite useful to use instead of the bracket notation, which is to get the last element of the array, with our traditional bracket notation we need to find the array length and deduct 1 to get the last element, but this new At() method makes this process even easier by writing negative number in our case simply use -1 to get the last element
Looping Arrays
We already know how to loop over an array using the for...of loop, but the forEach is really fundamentally different. Let's start with an example,
let numbers = [200, 450, -400]
//using for..of loop
for (const number of numbers){
if(number > 0){
console.log(`${number} is positive`);
}else{
console.log(`${number} is negative`);
}
}
//using forEach loop
numbers.forEach((number,index) => {
if(number > 0){
console.log(`${number} at index ${index} is positive`);
}else{
console.log(`${number} at index ${index} is negative`);
}
});
Output:
200 at index 0 is positive
450 at index 1 is positive
-400 at index 2 is negative
forEach method will loop over the array and in each iteration, it will execute the callback function. In each iteration, it will pass in three arguments, which are the current element , current Index of that element and the entire array that we are looping as an arguments to the callback function. However, we just don't need to pass these three arguments all the time. if want we can pass only current element or current element and current index or all three together.
To access the current index of the current element in the for..of loop, we use arr.entries() method, which returns an array of arrays, in each array element, first position contains the current index and second position contains the the value itself. But accessing current index of the element is much easier when we use forEach loop than for...of loop.
Another difference between these two loops is that we can not break out of a for loop which means that continue or break statements do not work in a forEach loop at all. So if we want to break out of the loop, then we will have to use for...of loop.
ForEach with Maps and Sets
ForEach is also available on maps and sets.
consider the following example,
const currencies = new Map([
['USD','United States dollar'],
['EUR','Euro'],
['GBP','Pound Sterling'],
])
currencies.forEach((value, key, map) => {
console.log(`${key}:${value}`);
})
Output:
USD:United States dollar
EUR:Euro
GBP:Pound Sterling
consider the following example,
const currenciesUnique = new Set(['USD','GBP','USD','EUR','EUR'])
console.log(currenciesUnique);
Output:
Set(3) { 'USD', 'GBP', 'EUR' }
currenciesUnique.forEach((value, key, sets) => {
console.log(`${key}:${value}`);
})
Output:
USD:USD
GBP:GBP
EUR:EUR
Data Transformations: Map, Filter and Reduce
There are three big and important array methods that we use all the time to perform data transformation. Basically, these three methods that we use to create new arrays based on transforming data from other arrays and these are map, filter and reduce.
#Map
The map() method returns a new array containing the results of applying an operation on all the original array elements. For example, we have an array of numbers and we say that each element shall be multiplied by two. With this condition in place, the map method multiplies every single element by two and puts it into a new array.
Consider the following array, where we have an array of names, now we need to compute a username and the username is simply the initials of the users.
const accounts = [
{
name: 'Jonas Schmedtmann',
},
{
name: 'Jessica Davis',
},
{
name: 'Steven Thomas Williams',
},
{
name: 'Sarah Smith',
},
];
const calculateUserName = (name) => {
let userNameArr = name.toLowerCase().split(" ");
const userNameFirstLetterArr = userNameArr.map((name) => name[0]);
return userNameFirstLetterArr.join("");
}
accounts.map((account) => {
account.userName = calculateUserName(account.name)
});
console.log(accounts);
Output:
[
{ name: 'Jonas Schmedtmann', userName: 'js' },
{ name: 'Jessica Davis', userName: 'jd' },
{ name: 'Steven Thomas Williams', userName: 'stw' },
{ name: 'Sarah Smith', userName: 'ss' }
]
The filter() method returns a new array containing the array elements that passed a specified test condition. For example, we have an array, which contains numbers and we are only looking for elements greater than 50. So, all the elements that pass the condition, will make it into a new filtered array. In other words, elements which satisfy the condition will be included in a new array that the filter() method returns. All other elements will get filtered out so, they will not be included in the new array.
Consider following array, where we have an array of number with both positive and negative numbers, lets filter the array with only positive numbers.
const numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
const postiveNumbers = numbers.filter((number) => number > 0);
console.log(postiveNumbers);//[ 200, 450, 3000, 70, 1300 ]
The reduce() method boils(reduces) down all the array elements into one single value. For example, add all the elements in an array which has only numbers. We need to specify an operation where we have an accumulator variable as the reduce method loops over the array, it keeps adding the current element into the accumulator until loop finishes. At the end, we will have total sum of all the elements
For example,
- we have an array of number with both positive and negative numbers, lets calculate the total of the array.
const numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
const total = numbers.reduce((accumulator, number) => accumulator + number,0);
console.log(total);//3840
- we have an array of number with both positive and negative numbers, lets find the maximum number from that array.
const numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
const maxNumber = numbers.reduce((accumulator, number) => {
if(accumulator > number){
return accumulator
}else{
return number
}
},0);
console.log(maxNumber);//3000
Chaining Map, Filter and Reduce
Let's have an example by chaining all these methods one after another. For example, we have an array of an account's deposits(positive number) and withdrawals(negative numbers), from that array we need to take all the deposits and then convert them from euros to dollars and finally add them all up so that we know how much was deposited.
let movements = [200, 450, -400, 3000, -650, -130, 70, 1300];
let eurToUsd = 1.2;
const totalDepositsUSD = movements.filter((movement) => movement > 0)
.map((movement) => movement * eurToUsd)
.reduce((acc, movement) => acc + movement,0);
console.log(totalDepositsUSD);//6024
#Find
The find() method allows us to retrieve one element from array based on a condition. Basically, it returns the first element that satisfies the condition. If no element in the array satisfies the condition then undefined is returned. The find() method also accept a callback function, which will then be called as the method loops over the array.
For example, we have an array of positive and negative number and let's find a number which is less than 0.
let numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
let negativeNumber = numbers.find(number => number < 0);
console.log(negativeNumber);//-400
let postiveNumber = numbers.find(number => number > 10000);
console.log(postiveNumber);//undefined
As you can see, -400 is the very first value that appears in the array that is negative and also it returns undefined if the condition did not satisfy any element in the array
The findIndex() method works almost the same as the find() method, but findIndex() method returns the index of the found element from the array based on a condition. Basically, it returns the first element that satisfies the condition. If no element in the array satisfies the condition then -1 is returned. The findIndex() method also accept a callback function, which will then be called as the method loops over the array.
For example, we have an array of positive and negative number and let's find the index of the number which is less than 0.
let numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
let negativeNumber = numbers.findIndex(number => number < 0);
console.log(negativeNumber);//2
let postiveNumber = numbers.findIndex(number => number > 10000);
console.log(postiveNumber);//-1
Some and Every
#Some
As we learned earlier, we use includes() method to test if an array includes a certain value, but if we want to test for a condition instead? that's where this some method comes into play. So, we use some() method to test whether at least one element in the array passes the condition. It returns true, if it finds an element for which the provided function returns true, otherwise it returns false.
For example, we have an array of positive and negative number and find if there is any positive number in the array.
let numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
let anyPostiveNumber = numbers.some(number => number > 0);
console.log(anyPostiveNumber);//true
let anyPostiveNumberTwo = numbers.some(number => number > 10000);
console.log(anyPostiveNumberTwo);//false
The every() method similar to the some() method, but it will return true if all of the elements in the array satisfy the condition, that we passed in. In other words, if every elements passes the test in our callback function, the every() method returns true otherwise it returns false.
For example,
let numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
let postiveNumber = numbers.every(number => number < 10000);
console.log(postiveNumber);//true
let postiveNumberTwo = numbers.every(number => number > 0);
console.log(postiveNumberTwo);//false
#Flat
Let's say we have an nested array and if we wanted to take all these elements in the separate and put all of these together in just one big array. This is pretty simple using the flat method. So, the flat() method creates a new array with all sub-array elements into one array.
For example,
let numbers = [[200, 450],[ -400, 3000, -650, -130], 70, 1300];
console.log(numbers.flat());//[200, 450, -400, 3000, -650, -130, 70, 1300];
However, let's say that we have an array which is even deeper nested like shown below,
let numbers = [[200, 450], -400, [3000, -650,[ -130, 70, 1300]]];
we apply flat() method on this array, we will get following result,
console.log(numbers.flat());//[ 200, 450, -400, 3000, -650, [ -130, 70, 1300 ] ]
By default, the flat() method goes one level deep, when flatting the array. However, we can fix this by using the depth argument. if don't specify the depth argument in flat() method, by default it takes value 1. As you can see, as the above the array, which has 2 levels deep, we need to pass 2 as depth argument into flat() method.
console.log(numbers.flat(2));//[ 200, 450, -400, 3000, -650, -130, 70, 1300 ]
However, what if we don't know the depth of an array. In that case, we just need to pass "Infinity" as an argument in falt() method. for example,
let numbers = [[200, 450], -400, [3000, -650,[ -130, [70, 1300]]]];
console.log(numbers.flat(Infinity));//[ 200, 450, -400, 3000, -650, -130, 70, 1300 ]
The flatmap() method essentially combines, a map and a flat method, into just one method. So, flatmap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level. it is identical to a map() followed by a flat() of depth 1, but slightly more efficient than calling those two methods separately.
For example, we have 4 different accounts and each has it's own movements. Our target is to combine all the movements into one long array.
const account1 = {
movements: [200, 450, -400]
};
const account2 = {
movements: [5000, 3400, -150]
};
const account3 = {
movements: [200, -200, 340]
};
const account4 = {
movements: [430, 1000, 700]
};
const accounts = [account1, account2, account3, account4];
const accountMovements = accounts.map(acc => acc.movements);
console.log(accountMovements);
const allMovements = accountMovements.flat();
console.log(allMovements);//[200, 450, -400, 5000,3400, -150, 200, -200,340, 430, 1000, 700]
//with flatMap
const onlyMovements = accounts.flatMap(acc => acc.movements);
console.log(onlyMovements);////[200, 450, -400, 5000,3400, -150, 200, -200,340, 430, 1000, 700]
#Sort
The sort() method allows us to sorts the elements of an array. But, it does mutate the origin array.
Consider the array of strings,
const fruits = ["Apple","Mango","Kiwi","Watermelon","Strawberry"];
fruits.sort();
console.log(fruits);//[ 'Apple', 'Kiwi', 'Mango', 'Strawberry', 'Watermelon' ]
As you can see that, array sorted alphabetically from A to Z and also original array is now mutated
Consider the array of numbers,
const numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
numbers.sort();
console.log(numbers);//[ -130, -400, -650,1300, 200, 3000,450,70 ]
If we take a look at the result, it is not really what we are expecting and these numbers are not at all ordered in the any way. The reason for this that the sort() method does the sorting based on strings. Basically, what it does is to convert everything to strings and then it does the sorting itself. If we look at the result as if they were strings, the minus always comes first. So, it alphabetically order them(1(30),4(00),6(50)) and then order the positive numbers like (1(300),2(00),3(000),4(50),7(0)).
However, we can fix this by passing a compare callback function. For example,
const numbers = [200, 450, -400, 3000, -650, -130, 70, 1300];
//if return < 0 a,b
//if return > 0 b,a
numbers.sort((a,b) => {
if(a > b){
return 1;
}
if(b > a){
return -1;
}
});
console.log(numbers);//[ -650,-400,-130,70,200,450,1300, 3000 ]
This callback function accepts two parameters which are the current value and the next value. when compare these two numbers, if it returns a negative value, then a will be put before b and if returns a positive value, then b will be put before a in the sorted output array.
NOTE: To sort the elements in an array without mutating the original array, use toSorted() method
More Ways of Creating and Filling Arrays
Generally, we create an array as shown below,
console.log([1,2,3,4,5,6,7,8])//[1,2,3,4,5,6,7,8]
console.log(new Array(1,2,3,4,5,6,7,8));////[1,2,3,4,5,6,7,8]
However, in these cases we already have data, therefore we could then manually create these arrays. However, we can actually also generate arrays programmatically, without having to define all the items manually. There are multiple ways of doing it,
use Array() constructor
const arr = new Array(7);
console.log(arr);//[ <7 empty items> ]
we might think that this is going to create an array with only one element 7, instead it creates a new array with 7 empty elements. Whenever, we pass only one argument into Array(), it creates a new empty arguments with that length. So, if we don't know about this behaviour of the Array() constructor then this can lead to weird errors.
There is only way to fill this array is to call fill() method. For example,
const arr = new Array(7);
console.log(arr);//[ <7 empty items> ]
console.log(arr.map(() => 3))//[ <7 empty items> ] -- no diff even if we use map
arr.fill(3);
console.log(arr);//[3, 3,3,3,3,3,3]
We need to pass a value and then it will fill up the entire array with this specific value and it does actually mutate the undelaying array.
Beside this value we can also specify where we want to begin the the array with. So, we can specify a begin parameter and then it only starts at index that we specify and it will then fill it up until the end unless we specify an end parameter but the final index is not gonna be included in the array.
For example,
- We need to start filling from index 3
const arr = new Array(7);
arr.fill(1,3);
console.log(arr);//[ <3 empty items>, 1, 1, 1, 1 ]
- We need to start filling from index 3 until 6
const arr = new Array(7);
arr.fill(1,3,6);
console.log(arr);//[ <3 empty items>, 1, 1, 1, <1 empty item> ]
We can also fuse the fill() method on other arrays as well and it does not have to be an empty array. for example,
const arr = [1,2,3,4,5,6,7];
arr.fill(23,2,5);
console.log(arr);//[1,2,23,23,23,6,7];
With this, we created an array programmatically, without actually having to write it down manually
This method allow us to create a new array from an iterable or array-like object. It actually accept 3 parameters which are
- arrayLike(Required): An iterable or array-like object, which we need to convert to an array
- mapFunction(optional): A function to call on every element of the array. if we provide this function, then the return value is added to the array. This function accept two parameters which are current element and index of the current element
- thisArgument(optional): value to use as this when executing mapFunction.
For example,
const arr = Array.from({length: 7}, () => 1);
console.log(arr);//[1, 1, 1, 1, 1, 1, 1]
const arr1 = Array.from({length: 7}, (element,i) => i+1);
console.log(arr1);//[1, 2, 3, 4, 5, 6, 7]
//In this case, we are not using the current element in map function so we can use underscore
const arr2 = Array.from({length: 7}, (_,i) => i+1);
console.log(arr2);//[1, 2, 3, 4, 5, 6, 7]
const arr3 = Array.from([1,2,3,4,5,6,7], (curElement,i) => curElement+i);
console.log(arr3);//[1, 3, 5, 7, 9, 11, 13]
const arr4 = Array.from("javascript");
console.log(arr4);//['j', 'a', 'v', 'a','s', 'c', 'r', 'i','p', 't']
Which Array Method to Use?
Methods to mutate the original array
- Add elements to the original array
- .push() -> at the end
- .unshift() -> at the beginning
- Remove elements from the original array
- .pop() -> at the end
- .shift() -> at the beginning
- .splice() -> at any position
- Others methods
- .reverse()
- .sort()
- .fill()
Methods to use/create a new array
- Calculate/Compute one array from the original array
- .map() -> it loops over the entire array
- Filtering an array using a condition
- .filter()
- taking a portion/slice of the original array
- .slice()
- Adding Original to other array Or Concatenating two or more arrays
- .concat()
- Flattening the original
- .flat()
- .flatMap()
Methods to find An array index
- Based on Value
- .indexOf()
- Based on test condition
- .findIndex() -> search for an element in the array based on the condition that we provide in the callback function and return index
Methods to find An array Element
- Based on test condition
- .find() -> search for an element in the array based on the condition that we provide in the callback function and return element
Methods to know if array includes a certain element or not
- Based on value
- .includes() -> return true if an array contains a single value
- Based on test condition
- .some() -> return true if at least one of the elements in the array satisfies the condition
- .every() -> return true if all of the elements in the array satisfies the condition
Methods to transform an array into a new string
- Based on separator string
- .join()
Methods to transform an array into a value
- Based on accumulator
- .reduce() -> Boil down to single value of any type: number, string, Boolean, or even new array or object
To loop array
- Based on callback
- .forEach() -> does not create a new array, just loop over it
EXERCISES
I have attached the github link with examples with answers. I hope it will help you to understand the concept further.
Next Article: JavaScript: Numbers, Dates and Timers
364 views