The way to understand the callback thing is to dive into parameterization:
Imagine you have these repeated patterns across your codebase:
for currentItem in list
newItem = currentItem + ' has changed'
push newItem to newList
for currentNumber in numberList
squaredNumber= currentNumber * currentNumber
push squaredNumber to listOfSquares
for personObject in personList
currentFirstName = personObject.firstName
push currentFirstName to onlyNamesList
As you can see, the code is reapeted a lot so you can make your own function that âtransformsâ each element in a list and gives you a new list with the transformed elements.
The question to ask is: What is the thing that is NOT COMMON and the thing(s) that ARE COMMON in the operation?
-
Common: Looping over the list -> Transforming each element -> Sending the result to a new list -> Return new list upon completion
-
Not common: The transformation itself since it varies from use case to use case.
Then, whatâs the solution? Parameterizing the actual transformation step that isnât common between all cases. The things that are common go inside the function âmapâ and you pass an âaction/transformer/mapperâ (depends on your favourite terminology) to said function along with the list you want to transform.
JavaScript allows you to store functions inside variables, pass them as arguments to functions, return them from functions and treat them as values (this concept is called "functions as first-class objects). The concept of a higher order function is a function that takes function(s) as parameters, and map happens to be one of those.
Now we can refactor our codebase as follows:
function square (x) = x * x
function changeText (str) = str + ' has changed'
function extractFirstName (person) = person.firstName
function map (transformation, list)
transformedList = []
for currentElement in list
transformedElement = transformation(currentElement)
push transformedElement to transformedList
return transformedList
map(square, [1 2 3]) # [1 4 9]
map(extractFirstName, [
{ firstName: 'Diego', lastName: 'Rivera' },
{ firstName: 'Lou', lastName: 'Vega' }
]) # ['Diego', 'Lou']
map(addSomeText, ['the world', 'my soul'])
# ['the world has changed', 'my soul has changed']
As you can see, the core concept is that you execute the function that you pass to map
inside of it and send the returning value to a new list consisting of transformed elements. Itâs like if you had an assembly line and you could replace the people working on it with different people that do what you want to your items at any given time according to your needs.
The difference between map
and forEach
is that forEach
does not return a new list, it only does âsomethingâ with every item, and filter
instead of accepting a transformation, it accepts what we programmers call a predicate
which is just a function that returns a boolean.
Higher order functions for the 4 most used array methods:
1- Reduce: Takes a reducer function that âfoldsâ a list into an accumulated result, this method is very powerful and the other 3 can be declared in terms of this one: (accumulator, item) returns <Accumulated Value>
.
2- Filter/Reject: Takes a predicate which evaluates every item and returns either true/false to decide if it stays or goes: (item) returns Boolean
.
3- ForEach: Takes an action
and this one doesnât return anything, it just performs a side-effect with each item: (item) returns Void
.
4- Map: Already explained what a mapper is lol: (item) returns <Transformed Item>
.
5- Find: Takes a predicate (or âsearch conditionâ) as well, but instead of returning a new list, it returns the first item that makes the predicate return true
otherwise it returns undefined
(I think).