All posts

Array methods (JS/TS)

📅 Created 6 years ago → updated after 6 years

🏷️ #js #ts #archive

.map()

Generates a new array from existing one. Think

[ x, x, x ] --- map() ---> [ y, y, y ]

Best use cases: convert an array of X-es to an array of Y-s. The argument of the .map() behaves like a predicate see below with a little difference: it can return any value. The argument of the .map() should be puresee below.

Mind that .map() always generates new array (although the it’s values might be the same as original ones):

const source = [ {}, {}, {} ]
const dest = source.map(x => x)

dest === source;         // false
dest[0] === source[0];   // true

.forEach()

Invokes side activity (AKA “side effects”) for every item of the array. Think

[ x, x, x ] --- forEach() ---> <undefined>
                 :
                 :---> tick!
                 :
                 :---> tick!
                 :
                 :---> tick!

Best use cases: logging values, “tapping” on every value. Avoid changing array values in .forEach() although impure predicates are welcome.

.filter()

It seems obvious. Think

[ x, y, z ] --- filter() ---> [ x, z ]

It returns the new array and does not modify source. The new one usually has different length; it’s possible that .filter() returns zero length array. The content of the destination array is triple-equal to the content of source array:

const source = [ {x: 1}, {x: 2}, {x: 3} ]
const dest = source.filter(el => el.x === 1)

dest[0] === source[0];      // true

The predicate of the .filter() should be pure and should not mutate array elements.

.find(), .findIndex(), .indexOf()

Finds the first entry that matches the predicate function. Think

[ x, y, z ] --- find() ---> x

Best use cases: pick the element and forget the array. If the relation to the array matters (e.g., there is an intention to modify the array later), think about .findIndex():

const source = [ {x: 1}, {x: 2}, {x: 3} ]
const idx = source.findIndex(el => el.x === 2)      // 1
const found = source[idx]

// do something with `found`
source.splice(idx, 1);          // remove the element from the array

Contrary to the .findIndex(), the .indexOf() searches the element by value not by predicate.

[ x, y, z ] --- findIndex() ---> 1
     :                           :
     :...........................:

Both .indexOf() and .findIndex() return number 0 ≤ N < array.length or -1 if element was not found.
Avoid using .indexOf() and .findIndex() for checking if the element exists in the array: there are more semantic methods.

.some()

Behaves in pretty similar way to .find() but answers the question “Is there any element matching the predicate or isn’t it?” without referring to the element. Think

[ x, y, z ] --- some() ---> y/n

.includes() is an edge variant of .some(). It also returns boolean value but expects the value itself, not a predicate function.

The opposite method, .every() returns boolean value answering the question whether every element matches the predicate.

Important

The .every() always traverses the whole array,
whilst the .some() exits early when succeeded.

Therefore,

if (!array.every(...))      // 💩
if (array.some(...))        // 🏎️ potentially faster

.reduce()

This is the most powerful one. It folds/warps the array into a single value, usually referred as “accumulator”. Think

[ x, y, z ] --- reduce() ---> accum
                  :            /:\
                  :           / : \
                  :... x ...:   :  :
                  :             :  :
                  :... y .......:  :
                  :                :
                  :... z ..........:

The most inspiring is that the accumulator can be of any type!
It cam be a number (e.g., to sum all the values or to calculate the min/max/average).
It can be the object so the array gets completely decomposed (see the example below).
It can be another array — so we can emulate .map() or .filter() 🤩


Mind that all the methods above don’t modify the source array. If they return an array, they might be chained (mind the terminating method returning a non-array):

"The Quick Brown Fox Jumps Over The Lazy Dog"
  .split("")
  .map(c => c.toUpperCase())
  .filter(c => /[AEIOU]/.test(c))
  .reduce((accum, c) => {
    accum[c] = (accum[c] || 0) + 1
    return accum
  }, {})

Following methods do modify the source array:

  • .push(), .pop(),
  • .shift(), .unshift(),
  • .splice(),
  • .sort(), .reverse()

Don’t be tricked by what they return; their return value has very little in common with the source array!

May the force be with you!


References

Predicate

A predicate is a function that receives an element (and optionally other arguments) and returns a boolean. The predicate functions of array methods look like this:

(element: T, index: number, wholeArray: T[]) => boolean

The array methods, e.g., .filter(), apply the predicate to every array member one by one.

The argument of the .map() is technically not a predicate because it returns an arbitrary type (it might be a boolean though.

Pure vs. impure functions

A pure function is self-sufficient and minimally depends on the outer world. It uses only the arguments or constants for the inner logic. It does not “modify” the outer world (in terms, “don’t cause side effects”).
Think,

const pure = (x) => x > 42 
const impure = (x) => x > (new Date().getSeconds()) 

The pure function is a perfect candidate for testing. Being run multiple times with same arguments it returns the same value See the “idempotence”.

Think Reactive Promises (part two): the legend of the lost Promise