As JavaScript developers, we're constantly dealing with Arrays. The most impactful skill set you can master in regards to Arrays in JavaScript is getting comfortable with most of the methods on Array.prototype
. This post is a collection of Array methods I think are crucial to know if you're a JavaScript developer.
.concat
Used to merge two or more arrays. What's nice about concat
is it doesn't mutate the original array but instead returns a new array.
const oldFriends = ['Jake', 'Mikenzi', 'Jessica']const newFriends = ['Merrick', 'Cash']const friends = oldFriends.concat(newFriends)oldFriends // Jake, Mikenzi, JessicanewFriends // Merrick, Cashfriends // Jake, Mikenzi, Jessica, Merrick, Cash
Because concat
doesn't modify the original array and instead returns a new array, it's commonly used in React apps, Redux apps, or anywhere else mutations are frowned upon.
addFriend(friend) {this.setState((prevState) => ({friends: prevState.concat([friend])}))}
.every
Used to determine if every element in an array passes a test specified by a given function. The function passed to every
gets invoked once for each element in the array. As soon as that function returns a falsy value, every
will stop executing and return false
. If the function passed to every
never returns a falsy value, then every
will return true.
const ages = [18,21,28,34,22]const groupCanVote = ages.every((age) => {return age >= 18})const groupCanDrink = ages.every((age) => {return age >= 21})console.log(groupCanVote) // trueconsole.log(groupCanDrink) // false
.fill
Defined, fill
"fills all the elements of an array from a start index to an end index with a static value". It sounds pretty useless, but I've found one scenario where it's pretty helpful.
As you start to make your code more functional, you naturally start to avoid using regular for loops in favor of .map
, .forEach
, etc. Let's say our use case was we wanted to execute a function an arbitrary amount of times, say 10. How could we do this and avoid using a for loop? Your first intuition might be to use an Array constructor with .map.
Array(10).map(() => {return doThing()})
You'll notice if you run that code it won't work. When you do Array(10)
you're creating an array of 10 unset or empty
values. When you use any of the array methods, they won't work on an array with empty values. This is where .fill
comes into play. If you do Array(10).fill()
, you'll then get an array with 10 undefined
, not empty items.
Array(10).fill().map(() => {return doThing()})
Not super useful, but good to know it exists if you need it.
.filter
Creates a new array after filtering out elements that don't pass a test specified by a given function.
const tweets = [{ id: 1, stars: 13, text: 'Turns out "git reset --hard HEAD^" was a terrible idea.' },{ id: 2, stars: 87, text: 'Tech conferences are too expensive.' },{ id: 3, stars: 51, text: 'Clean code is subjective. Optimize for deletion.' },{ id: 4, stars: 19, text: 'Maybe the real benefit of open source was the friendships we made along the way?' },]const popularTweets = tweets.filter((tweet) => {return tweet.stars > 50})
filter
is popular in React and Redux code bases (or anywhere where immutability is required) since it doesn't modify the original array, instead, it returns a new array.
removeItem(index) {this.setState((prevState) => ({todos: prevState.todos.filter((item, i) => i !== index)}))}
.find
Allows you to find
the first element in an array which satisfies a test specified by a given function.
const tweets = [{ id: 1, stars: 13, text: 'Turns out "git reset --hard HEAD^" was a terrible idea.' },{ id: 2, stars: 87, text: 'Tech conferences are too expensive.' },{ id: 3, stars: 51, text: 'Clean code is subjective. Optimize for deletion.' },{ id: 4, stars: 19, text: 'Maybe the real benefit of open source was the friendships we made along the way?' },]const tweet = tweets.find((t) => t.id === 3)console.log(tweet) // {id: 3, stars: 51, text: "Clean code is subjective. Optimize for deletion."}
.findIndex
Similar to .find
, but instead of returning the element, it returns the index where the element is located.
const tweets = [{ id: 1, stars: 13, text: 'Turns out "git reset --hard HEAD^" was a terrible idea.' },{ id: 2, stars: 87, text: 'Tech conferences are too expensive.' },{ id: 3, stars: 51, text: 'Clean code is subjective. Optimize for deletion.' },{ id: 4, stars: 19, text: 'Maybe the real benefit of open source was the friendships we made along the way?' },]const index = tweets.findIndex((t) => t.id === 3)console.log(index) // 2
.forEach
Invokes a provided function once for each item in the array. Note .forEach
is similar to .map
except .map
returns a new array, .forEach
has no return value. Generally, you can use forEach
as a replacement for a for loop.
const friends = ['Jake', 'Mikenzi', 'Jacob']friends.forEach((friend) => addToDOM(friend))
.includes
Determines if a particular value is found in an array. Note: We used to use indexOf
for this same behavior but as of ES6, includes
is more intuitive.
const friends = ['Jake', 'Mikenzi', 'Jacob']friends.includes('Jake') // truefriends.includes('Karl') // false
.indexOf
Returns the index of a particular value. If that value doesn't exist in the array, it returns -1.
const friends = ['Jake', 'Mikenzi', 'Jacob']friends.indexOf('Jake') // 0friends.indexOf('Karl') // -1
.join
Turns (or joins) all elements of an array into a string and returns it. By default, when converted into a string, each item in the array will be separated by a comma. You can change that by passing in a custom separator as the first argument.
const splitSentence = ['learn', 'react', 'at', 'ui.dev']splitSentence.join() // learn,react,at,ui.devsplitSentence.join(' ') // learn react at ui.dev
.map
Creates a new array with the result of calling a provided function on every element in the original array. When should you use .map
? I use it when I need to create a new array, based on a previous array. In the example below, I want to create a new array, tweetIds
, based on the original array, tweets
.
const tweets = [{ id: 1, stars: 13, text: 'Turns out "git reset --hard HEAD^" was a terrible idea.' },{ id: 2, stars: 87, text: 'Tech conferences are too expensive.' },{ id: 3, stars: 51, text: 'Clean code is subjective. Optimize for deletion.' },{ id: 4, stars: 19, text: 'Maybe the real benefit of open source was the friendships we made along the way?' },]const tweetIds = tweets.map((tweet) => tweet.id) // [1,2,3,4]
In React, .map
is used a lot along with JSX to create an unordered list.
render () {return (<ul>{this.state.todos.map((todo) => {return <li key={todo.id}>{todo.text}</li>})}</ul>)}
.pop
Removes the last element from an array and returns it. Favor using .filter
instead of .pop
as mutations are 👺.
const friends = ['Jake', 'Mikenzi', 'Karl']const removedFriend = friends.pop()console.log(removedFriend) // Karlconsole.log(friends) // ['Jake', 'Mikenzi']
.push
Adds an item to the end of the array and returns the array's new length. Favor using .concat
instead of .push
as mutations are 👺.
const friends = ['Jake', 'Mikenzi']friends.push('Jordyn') // 3console.log(friends) ['Jake', 'Mikenzi', 'Jordyn']
.reduce
.reduce
holds the keys to the universe. If you master it, you'll be able to do pretty much anything you want with arrays. Before you even look at the API, it's important to understand why .reduce
exists. The idea of .reduce
is that you can take an array and transform it into anything else - another array, an object, an integer, literally anything. Why would you ever want to do that? Look at every single example on this whole page. In each one, we're taking an array and transforming it in some way - mostly to another array. Let's look at some other common ways you'd transform an array.
[1,2,3] -> sum-> 6---[{ name: 'Tyler', age: 28},{ name: 'Mikenzi', age: 26},{ name: 'Blaine', age: 30 }] -> Just the names -> ['Tyler', 'Mikenzi', 'Blaine']---[{ name: 'Tyler', age: 28},{ name: 'Mikenzi', age: 26},{ name: 'Blaine', age: 30 }] -> Length and age count -> { users: 3, ageTotal: 84}---[{ id: 1, stars: 13, text: 'Turns out "git reset --hard HEAD^" was a terrible idea.' },{ id: 2, stars: 87, text: 'Tech conferences are too expensive.' },{ id: 3, stars: 51, text: 'Clean code is subjective. Optimize for deletion.' },{ id: 4, stars: 19, text: 'Maybe the real benefit of open source was the friendships we made along the way?' },]-> Remove the stars property ->[{ id: 1, text: 'Turns out "git reset --hard HEAD^" was a terrible idea.' },{ id: 2, text: 'Tech conferences are too expensive.' },{ id: 3, text: 'Clean code is subjective. Optimize for deletion.' },{ id: 4, text: 'Maybe the real benefit of open source was the friendships we made along the way?' },]
Here's how I think about whether I should use .reduce
or not.
1) Am I transforming an array into another arrayjust by removing some elements? Use .filter2) Am I transforming an array into another array? Use .map3) Am I transforming an array into something other than another array? Use .reduce
Now that we understand what .reduce
is used for, let's take a look at the API. Don't worry if this is confusing, with great power comes great initial confusion. Google "reduce JavaScript" and read the first two pages of results. It'll be worth it - promise.
I'll first show you how to accomplish our goal of summing up an array of number into a single integer, then we'll walk through it.
function sum (arr) {return arr.reduce((total, num) => {return total + num}, 0)}sum([1,2,3]) // 6sum([5,5,5]) // 15
Remember, the goal here is to take the array that's being passed into the sum
function and transform it into a single integer, which is the summation of all the numbers in the array.
The first thing you'll notice is that we pass .reduce
two arguments. The first is a function that will be invoked for every element in the array. The second is what we'll call the "initial value". In our example, because we're adding all the numbers together, we want the initial value to be 0 (so we don't run into any NaN problems). If we were creating a new object, the initial value would be an empty object. If we were creating an array, it would be an empty array.
Next, we need to figure out what the parameters are for the function we pass to .reduce
. In our example, we named them total
and num
. Remember, this function is going to be called for each element in the array. The reason we named it num
is because we're reducing over an array full of integers and for each iteration, num
is going to be whatever the number is in the array. We can see this in the example below.
function sum (arr) {return arr.reduce((total, num) => {console.log(num)}, 0)}sum([1,2,3])// 1// 2// 3
That makes sense. The next thing we need to figure out is total
. This is where it gets a little mind-bendy. In our example, total
is going to initially be 0, since that's what we set our initial value to. After that, total
is going to be whatever the previous iteration returned. We can see this clearly in the example below.
function sum (arr) {return arr.reduce((total, num) => {console.log(total)return Date.now()}, 0)}sum([1,2,3])
The first time our callback function runs, it logs 0 to the console. Again, that's because we set the initial value to 0. Then, for each new time the function runs, we get a timestamp in the console because that's what we're returning.
Now let's take this knowledge and look back at our initial solution.
function sum (arr) {return arr.reduce((total, num) => {return total + num}, 0)}sum([1,2,3])
The very first time the callback function runs, total
will be 0 and num
will be 1. Then, the next iteration, total
will be 1 and num will be 2
. The reason total
is one is because we previously returned 0 + 1
. Then, total
will be 3 and num
will be 3. Then, there are no more elements in the array so what gets returned is 6. We can see this in the diagram below.
Initial Value: 0First iteration:total: 0num: 1Second iteration:total: 1num: 2Third iteration:total: 3num: 3No more elements in the array, return 3 + 3 which is 6.
Let's look at the example where we convert an array of objects into another object.
[{ name: 'Tyler', age: 28},{ name: 'Mikenzi', age: 26},{ name: 'Blaine', age: 30 }] -> Length and age count -> { users: 3, ageTotal: 84}
First, what do we want the initial value to be? We want it to be an object with a users
property as well as an ageTotal
property, both set to 0
.
function getUserData (users) {return users.reduce(() => {}, { users: 0, ageTotal: 0 })}
Now we need to figure out what each iteration looks like. We know what we want to return is the initial object, adding 1 to the users
property and adding whatever the user's age is to the ageTotal
property.
function getUserData (users) {return users.reduce((data, user) => {data.users += 1data.ageTotal += user.agereturn data}, { users: 0, ageTotal: 0 })}const users = [{ name: 'Tyler', age: 28},{ name: 'Mikenzi', age: 26},{ name: 'Blaine', age: 30 }]getUserData(users) // { users: 3, ageTotal: 84 }
Impressive, yeah? Odds are this is still a bit fuzzy. The best thing you can do is take an array, and practice transforming it into anything you can think of.
.reverse
Reverses the order of an array. It's important to remember that this method mutates the original array.
const letters = ['a', 'b', 'c']letters.reverse()console.log(letters) // ['c', 'b', 'a']
The most common use case I see for .reverse
is the interview question, "how do you reverse a string?"
const string = 'I like JavaScript'const arr = string.split('') // Convert the string into an array// Then reverse the array and join it back to a string.arr.reverse().join('') // "tpircSavaJ ekil I"
.shift
Removes the first element from an array and returns it. Favor using .filter
instead of .shift
as mutations are 👺.
const friends = ['Karl', 'Mikenzi', 'Jake']const removedFriend = friends.shift()console.log(removedFriend) // Karlconsole.log(friends) // ['Mikenzi', 'Jake']
.slice
Allows you to create a new array from a portion of an existing array. It also doesn't modify the original array.
.slice
takes two arguments. The first argument is the index of the beginning item you want to grab in the array and the second argument is the index of the end item you want to grab in the array, not inclusive. So for example, if you do .slice(1,4)
, the array that is returned will have whatever elements were in the 1st, 2nd, 3rd index in the original array, not the 4th.
const friends = ['Jake', 'Mikenzi', 'Jordyn', 'Cash', 'Leo']const bestFriends = friends.slice(1,4)console.log(bestFriends) // ['Mikenzi', 'Jordyn', 'Cash']
.some
Used to determine if any element in an array passes a test specified by a given function. The function passed to some
gets invoked once for each element in the array. As soon as that function returns a truthy value, some
will stop executing and return true
. If the function passed to some
never returns a truthy value, then some
will return false.
const ages = [6, 14, 12, 22, 13]const hasAdultSupervision = ages.some((age) => {return age >= 21})const canRentCar = ages.some((age) => {return age >= 25})console.log(hasAdultSupervision) // trueconsole.log(canRentCar) // false
.sort
Allows you to sort the elements of an array. This one is more complex than I'd like it to be. Here's my rule of thumb that works _most of the time_â„¢. If what you're sorting is based on string values, you can call .sort()
and you'd get what you'd expect.
const friends = ['Jake', 'Jacob', 'Mikenzi', 'Alex']friends.sort()console.log(friends) // ["Alex", "Jacob", "Jake", "Mikenzi"]
If you're wanting to sort an array of integers, you can pass in a function and return the first argument minus the second argument.
const ages = [21, 19, 35, 38, 18, 23]ages.sort((a,b) => a - b)console.log(ages) // [18, 19, 21, 23, 35, 38]
Now say we had a more complex array, something like this array of objects.
const users = [{ name: 'Jim', age: 28 },{ name: 'Alex', age: 32 },{ name: 'Mikenzi', age: 26 },{ name: 'Christina', age: 42 },]
To sort by the age, which is an integer, you would use the same pattern we used above, a - b
.
users.sort((a,b) => a.age - b.age)
To sort by name, you'd use a similar pattern, but instead of the minus sign, you'd use a greater than sign.
users.sort((a, b) => a.name > b.name)
Note that .sort
is mutative so each time you invoking it you're modifying the original array.
.splice
Allows you to add and or remove items from anywhere inside of an array. It's mutative so favor using another method that isn't mutative like .slice
, .map
, .filter
or the spread operator.
The API is a bit funky as well. The first argument specifies where to start, the second argument specifies how many elements to remove, and the third argument specifies what to add.
const friends = ['Jake', 'Karl', 'Mikenzi']const removedItems = friends.splice(1, 1, 'Jordyn')// Start at index 1, remove 1 item then add Jordynconsole.log(removedItems) // ['Karl']console.log(friends) // ['Jake', 'Jordyn', 'Mikenzi']
.unshift
Adds one or more elements to the beginning of an array and returns the array's new length.
const ages = [22,27,29]ages.unshift(20) // 4console.log(ages) // [20,22,27,29]
Favor using .concat
or the Array spread operator instead of .shift
as mutations are 👺.
const ages = [22,27,29]const newAges = [20].concat(ages)console.log(ages) // [22,27,29]console.log(newAges) // [20,22,27,29]
const ages = [22,27,29]const newAges = [20, ...ages]console.log(ages) // [22,27,29]console.log(newAges) // [20,22,27,29]
Bonus
.from
.from
isn't an array method on Array.prototype
but instead is a static method on the Array
class. So you access it with Array.from()
.
.from
is used to create a new array from an "array-like" or iterable object.
The most common use case where you see an "array-like" object is with the "arguments" keyword inside of a function.
function sumArgs() {console.log(arguments)return arguments.reduce((total, ele) =>total + ele,0) // Error!}sumArgs(1,2,3,4,5) // [1, 2, 3, 4, 5, callee: Æ’, Symbol(Symbol.iterator): Æ’]
Notice that arguments
looks like an array, but it also has some extra properties on it. So it's an "array-like" object.
Even though it's "array-like", that doesn't mean it's an instance of Array
which means it doesn't have access to the methods on Array.prototype
.
This is where Array.from
comes into play. We can pass our array-like object to Array.from
and what we'll get back is an actual instance of Array
.
function sumArgs() {return Array.from(arguments).reduce((total, ele) => total + ele, 0)}sumArgs(1,2,3) // 6