Sophie Au

Software Developer, Web Designer, Tea Enthusiast

Notes on JavaScript Arrays

15 March 2020

This is an almost-exhaustive list of all the array methods (that I could see on MDN) complete with snarky commentary and pointers on how to use them.

Static Methods

Array.from()

Array.from() is pretty cool in letting you create an array from (the surprise!) a bunch of items. There's a few ways to do that:

// splitting up a string
Array.from('merp')
// ["m", "e", "r", "p"]

// creating a new array from an old one
Array.from([1, 2, 3], x => x + x)
// [2, 4, 6]

// by just giving it a length and some default values
Array.from({length: 3}, x => "hello")
// ["hello","hello","hello"]

Array.of()

Creates an array from the elements that were passed in. Not really needed but it is a method on the Array type so meh.

Array.of(1,2,3,5)
// => [1,2,3,5]

// the same as
[1,2,3,5]

Array.isArray()

Checks if the passed-in parameter is an array.

Array.isArray([1,2])
// => true

Array.isArray(2)
// => false

Mutator Methods

These methods mutate the array in place. Use with caution! Most methods have non-mutating alternatives that work just as well.

copyWithin()

Replaces parts of an array with other parts of the same array. Not sure why you would do that but you can so yay.

// copy to index 0 the element at index 3 (the element at index 3 to the element before index 4)
[1,2,3,4,5].copyWithin(0, 3, 4)
// => [4,2,3,4,5]

// copy to index 1 all elements from index 3 to the end
[1,2,3,4,5,6].copyWithin(1, 3);
[1,2,3,4,5,6].copyWithin(1, 3, array.length);
// => [1,4,5,6,5,6]

fill()

Fills the array with values. Kinda like it says...

[1,2,3,5,6].fill(1)
// => [1,1,1,1,1]

pop()

Removes the last element from an array and returns that element

const elem = [1,2,3,4,5].pop()
// => elem = 5
//    array = [1,2,3,4]

// non-mutating version
const pop = (array: any[]) => {
  const lastElem = array[array.length - 1]
  const newArray = array.slice(0, array.length - 2)
  return [lastElem, newArray]
}

push()

Adds one or more elements to the end of an array

[1,2,3,4].push(5,6)
// => [1,2,3,4,5,6]

// non-mutating version
[...array1, all, the, elements]

reverse()

Reverses the elements of an array in place (i.e. it manipulates the current array)

[1,2,4,6].reverse()
// => [6,4,2,1]

// non-mutating version
const reverse = (array: any[]) => {
  let newArray = new Array(array.length)
  array.forEach((elem, i) => newArray[array.length - 1 - i] = elem)
  return newArray
}

shift()

Removes the first element in an array and returns it

[1,2,3].shift()
// => elem = 1
//    array = [2,3]

//non-mutating version 
const [elem, ...newArray] = [1,2,3]

sort()

Sorts the elements of an array in place (i.e. it manipulates the current array). By default it transforms all elements into strings and sorts them alphabetically (capital letters first followed by lower case). Note that numbers are sorted alphabetically too, meaning that 10 goes before 2. Can be changed though. The callback that's passed in compares the first two values and

  • if the result is < 0 puts a before b
  • if the result is > 0 puts b before a
  • if the result is == 0 keeps the items in the order they are

I looked into how exactly that works and it boils down to magic but it works so yay.

[1,4,20,4,0].sort()
// => [0,1,20,4,4]

[1,4,20,4,0].sort((a,b) => a - b)
// => [0,1,4,4,20]

splice()

Removes the specified number (default is 1) of elements at the specified index

[1,2,3,4,5,6,7].splice(2,1) // remove 1 element at index 2
[1,2,3,4,5,6,7].splice(2) // same as above
// => [1,2,4,5,6,7]

// non-mutating version
[...array.slice(0,2), ...array.slice(3)]

unshift()

Adds one or multiple elements to the beginning of an array

[1,2,3].unshift(-1,0)
// => [-1,0,1,2,3]

// non-mutating version
[-1, 0, ...array]

Accessor Methods

These methods do not modify the array, and return a new array (based on some representation of the original).

concat()

Just don't! instead use what's below. Much faster! For some reason. Just be aware that push modifies the firstArray which you might not want. Which is why I'm spreading in the example below.

const newArray = [...firstArray]
newArray.push(...SecondArray)

filter()

Does what it says on the tin. Goes through an array and removes all elements for which the predicate is false.

// only keep even numbers
[1,2,3,4,5,6,7].filter(num => num % 2)
// => [2,4,6]

flat()

If you have an array with arrays in it it will, well, flatten it... Can also flatten deeper if you set a param. Set Infinity to flatten all the things. By default the param is 1.

[4,3,[1,5,7],[2,6,3,[1,2,4],1]].flat(2)
// => [4,3,1,5,7,2,6,3,1,2,4,1]

[4,3,[1,5,7],[2,6,3,[1,2,4],1]].flat()
// => [4,3,1,5,7,2,6,3,[1,2,4],1]

//also:
[1,2,4, ,6].flat()
// => [1,2,4,6]

includes()

Checks if an element is in an array. Kinda like a very simple some.

// does the array contain the number 3
[1,2,3,4,5,6,7].includes(3)

// is essentially the same as
[1,2,3,4,5,6,7].some(num => num === 3)
// => true

indexOf(), lastIndexOf()

Essentially the findIndex of includes. Return the first/last index of a match.

[1,2,3,4,5,6,3].indexOf(3)
// => 2

[1,2,3,4,5,6,3].lastIndexOf(3)
// => 6

join()

Joins the elements in an array into a string using the specified separator. Default is ,.

[1,2,3,4,6].join()
//=> "1,2,3,4,6"

[1,2,3,4,6].join(" and ")
//=> "1 and 2 and 3 and 4 and 6"

slice()

Copies a portion of an array up to the specified length.

[1,2,3,4,5,6].slice()
// => [1,2,3,4,5,6]

// takes from 0 up to but NOT including 3
[1,2,3,4,5,6].slice(0,3)
// => [1,2,3]

// takes all it can.
[1,2,3].slice(0,5)
// => [1,2,3]

Iteration Methods

Allow you to iterate over the array directly or give you an iterator object that you can use in a for-loop.

every()

Gives back a boolean on whether or not EVERY element in an array fits the predicate

const users = [{id: 1, isAdmin: false},{id:2, isAdmin: false},{id:3, isAdmin: true},{id:4, isAdmin:true}]
users.every(user => user.isAdmin)
// => false since users 1 and 2 aren't admins

find()

Same as includes really except that it returns the first element where the predicate is true. It's kind of like filter() but only for the first value.

//return first admin
const users = [{id: 1, isAdmin: false},{id:2, isAdmin: false},{id:3, isAdmin: true},{id:4, isAdmin:true}]
users.find(user => user.isAdmin)
// => {id:3, isAdmin: true}

findIndex()

Same as includes and find really except that it returns the index of first element where the predicate is true.

const users = [{id: 1, isAdmin: false},{id:2, isAdmin: false},{id:3, isAdmin: true},{id:4, isAdmin:true}]
users.findIndex(user => user.isAdmin)
// => 3

flatMap()

Dodgy but more perfomant replacement for using arr.flat(1).map(x => doSomething(x)).

const arr1 = ["why is this", "a", "thing"];

arr1
  .map(x => x.split(" "))
// [["why","is","this"],["a"],["thing"]]
  .flat();
// ["why","is","this","a","thing"]

arr1.flatMap(x => x.split(" "));
// ["why","is","this","a","thing"]

forEach()

Not recommended. If you really feel like you need to use a for loop go with for (const v of arr). Here's why:

  • doesn't ignore empty elements in an array like [1,2,3,,5]. Note that null, undefined and false aren't empty elements.
  • forEach() messes up when you wanna do something async inside the function body.

If you wanna skip empty elements and don't care about async, use forEach().

[1,2,3,4].forEach((num, i) => 
    if (i > num) console.warn("index larger than value");
)

map()

My favourite thing to use. So handy. 😍

const resultArr = [1,2,3,4].map((elem, i) => `This is number ${elem} at position ${i}`)
/* => ["This is number 1 at position 0",
       "This is number 2 at position 1",
       "This is number 3 at position 2",
       "This is number 4 at position 3"]
*/

reduce(), reduceRight()

The devil incarnate. Does everything but fuck me if I ever remember the syntax. reduceRight does the same thing except from the right. Not that that clears anything up. Now, in theory you can go very simple with reduce which totally makes sense, like this one:

// syntax: array.reduce(callback, startingAccumulator)
// callback params: accumulator, currentVal, currentIndex (optional) 
[1,2,3,4].reduce((acc, val) => acc + val, 0)
// => 10

But that's not enough, no. If you really want to you can do everything with reduce. Like rebuild the map or filter function:

const map = (arr, callback) =>
    arr.reduce((mapArr, elem) => [...mapArr, callback(elem)], [])

const filter = (arr, callback) =>
    arr.reduce((filteredArr, elem) => callback(elem) ? [...filteredArr, elem] : filteredArr, [])

I mean, yeah, you can do that, but just because you can doesn't mean you should. And luckily in most cases you can just chain other array functions (like map and filter) together to get the same result. Now, in all seriousness, reduce can be quite nice and helpful but it is pretty hard to understand so use with caution!

some()

Gives back a boolean on whether or not even a single element is in an array that fits the condition.

// does the array contain the number 3
[1,2,3,4,5,6,7].some(num => num === 3)
// => true

entries() keys() values()

Also exist. They return iterators for:

  • entries: [index, value]
  • keys: index
  • values: value

Which you can use like this:

for (const value of [1,2,3,4].values()) {
  console.log(value);
}
// => logs: 1, 2, 3, 4

General Tips and Tricks

Remove falsey values from an array

[0, "blue", "", NaN, 9, true, undefined, "white", false].filter(Boolean);
// ["blue", 9, true, "white"]

Clear out an array

Note that if you use a length that's larger then the current one you're gonna create a holey array.

var fruits = ["banana", "apple", "orange"];
fruits.length = 2
// fruits = ["banana", "apple"]
fruits.length = 4
// fruits = ["banana", "apple", , ]
fruits.length = 0;
// fruits = []

Create a new array with default values

var newArray = new Array(3).fill(1);
// [1,1,1]