JavaScript higher-order functions
With functions we learned that we could pass in variables as "parameters", but with higher-order functions we can pass in other functions! Some built-in higher-order functions in JavaScript include:
forEach()
map()
filter()
reduce()
sort()
every()
some()
forEach
Suppose we have an array and we wish to iterate through each of its elements:
We could use a for
loop, or we could use a cleaner, high-order forEach
method:
const grades = [58, 60, 60, 90, 99]
// we could do this
for (let i = 0; i < grades.length; i++) {
console.log(grades[i] + 10)
}
// or we could do this
grades.forEach(function (grade) {
console.log(grade + 10)
})
// we could even do this to look even "more skilled"
grades.forEach(grade => console.log(grade + 10))
In the example above, notice how each block looks progressively simpler but does the same thing as the others!
Note that the forEach()
higher-order function:
- always returns
undefined
- iterates through each element and calls other functions to output a result
- does not modify the original array
- does not create a new array
- does not allow the use of a
break
map
Suppose we have an array and we wish to go through its elements, then perform the same operation on those elements...
We could use the built-in map()
function to iterate and transform:
// an array of grades in a class
const grades = [72, 75, 90, 91, 92, 92, 93, 94]
// everyone's grades go up by 10%
const bellCurve = grades.map((grade) => grade + 5)
console.log(grades)
// [72, 75, 90, 91, 92, 92, 93, 94]
// i.e. the original array does not change
console.log(bellCurve)
// [77, 80, 95, 96, 97, 97, 98, 99]
// the map creates a new array, separate from the "real world"
Note that map()
:
- returns the values into a new array
- the length of the new array will always be equal to the original array
- the original array stays the same
filter
Suppose we have an array and we wish to go through its elements, then perform remove elements that do not satisfy some condition...
We could use the built-in filter()
function to iterate and eliminate:
// an array of grades in a class
const grades = [72, 75, 90, 91, 92, 92, 93, 94]
// the callback function gets only "grades 90 and up"
const honors = grades.filter((grade) => grade >= 90)
console.log(grades)
// [72, 75, 90, 91, 92, 92, 93, 94]
// i.e. the original array does not change
console.log(bellCurve)
// [90, 91, 92, 92, 93, 94]
// the filter removes any values under 90 by the return value in the callback
Note that filter()
:
- returns the values into a new array
- the length of this array will always be equal or less than the original array
- the original array stays the same
reduce
Suppose we have an array and we wish to find the sum of all its values...
We could use the built-in reduce()
function to iterate and summarize:
// an array of grades in a class
const grades = [72, 75, 90, 91, 92, 92, 93, 94]
// the callback function gets only "grades 90 and up"
const gradeSum = grades.reduce((accumulator, currentGrade) =>
accumulator += currentGrade
)
const average = gradeSum / grades.length
console.log(grades)
// [72, 75, 90, 91, 92, 92, 93, 94]
// i.e. the original array does not change
console.log(average)
// 87.375 (i.e. 699 / 8)
So unlike map()
and filter()
, the callback function passed into reduce()
has two required parameters:
accumulator
- the running total of the function
currentValue
(shown ascurrentGrade
above)
Note that reduce()
:
- returns a single value
- by accumulating the values (most often by addition) of every element in the array
- the original array stays the same
sort
Suppose we have an array and we wish to sort its elements...
We could use the built-in sort()
function to iterate and re-arrange:
// an array of titles
const titles = ["January", "February", "March", "April"]
// the callback function gets only "grades 90 and up"
const sorted = titles.sort()
console.log(sorted)
// ["April", "February", "March", "January"]
By default, this method sorts crazily by:
- converting all values into strings
- and then by this hierarchy:
- arrays
- numbers
- (by digit, i.e. 1 is before 20, 20 is before 21, but 21 is before 9!)
- strings with uppercase letters
- objects
- curly braces fall in between uppercase and lowercase letters!
- strings with lowercase letters
null
is considered a string
undefined
- strangely, this is not a string
- empty slots
So, we can see this more clearly with an example:
const arrayMix = [ [], 99, 7, 'B', 'ZZZZ',
'P', NaN, {}, 'a', null, 224, null, undefined,
'x', ,'z'
]
console.log(arrayMix.sort())
/*
[
[], 224, 7, 99, 'B', NaN, 'P', 'ZZZZ', {}, 'a',
null, null, 'x', 'z', undefined, empty
]
*/
How do we sort numbers by amount (and not by their first digits)?
We would need to use a comparison function as a callback function when using sort()
:
const numbers = [25, 1, 118, 20, 92, 444, -1, 2]
// without comparison function
console.log(numbers.sort())
// [-1, 1, 118, 2, 20, 25, 444, 92] (!!!)
// with comparison function
console.log(numbers.sort((a,b) => a-b))
// [-1, 1, 2, 20, 25, 92, 118, 444] :)
This callback function (a, b) => a-b
simply:
- iterates through each pair of elements in the array
- compares the two values and does its rearranging in-place
- it creates no new arrays but changes the original array
every and some
Suppose we have an array and we wish to go through its elements, then find out if all elements meet a condition, or at least one does so...
We could use the built-in every()
and some()
functions to iterate and test for uniformity or existence:
// an array of names in a group
const names = ["Kenny", "Kyle", "Eric", "Butters"]
// find out if ALL the names have an "E" or "e"
const hasE = names.every((name) => name.toLowerCase().includes('e'))
console.log(hasE)
// true
// what if we don't use toLowerCase()?
const hasSmallE = names.every((name) => name.includes('e'))
console.log(hasSmallE)
// false
// find out if ANY of the names have a lowercase "e"
const eAnywhere = names.some((name) => name.includes('e'))
console.log(eAnywhere)
// true (Kenny, Kyle and Butters have a small "e")
So, both every()
and some()
:
- return a simple Boolean indicating
- a uniformity (in the case of
every()
) or - an existence (in the case of
some()
)
- a uniformity (in the case of
- the condition in question appears in the body of the callback function
- the callback function gets passed as the one argument