Underbar, explained.
Write your own _.each, _.map, and _.reduce methods in Javascript!
Underbar is a project to rewrite the Underscore Javascript library. This assignment is often given to new Javascript students, and it can be very challenging! Let’s walk through _.each, _.map and _.reduce together.
First, to be able to test these methods in repl.it and node, we need to reassign _. This will disable the expression assignment to _ , allowing us to rewrite it.
var _ = {};
Now, write your own _.each method. Use a loop.
function _.each(array, func){
for(var i = 0; i < array.length; i++){
func(array[i]);
}
}
The func variable represents the function that you are calling on *each* item in the array. _.each is used primarily for its side effects and doesn’t save the results anywhere — for that, we’ll use _.map. Map returns a modified array.
function _.map(array, func){
var final = [];
for (var i = 0; i < array.length; i++){
final.push(func(array[i]));
}
return final;
}
Pretty simple. But what if we wanted to use our _.each method to write our _.map function? We know _.each takes an array and a function. But what function?
This is a very common mistake, but _.each does NOT take the same function that was passed into _.map! The function that we pass into _.each will be an anonymous function that will perform the function on each element of the array. For _.map, we want to push each element into the final array so that’s what we’ll write in _.each’s anonymous function.
function _.map(array, func){
var final = [];
_.each(array, function(element){
final.push(func(element));
})
return final;
}
Beautiful. Now let’s make things a little tougher and tackle my favorite method: _.reduce! Reduce takes a collection, a function and an accumulator. These arguments need to be written in the order your specs call them. If you need a refresher on how reduce works, check out the reduce documentation.
Let’s use our _.each function that we wrote above. Remember _.each takes a collection and a function — but *not* the same function that is passed into reduce! As always, the anonymous function _.each takes is what we want to do to each item in the collection. (Underbar calls it a collection here, which is a great clue that what we write needs to work with an array or an object.)
function _.reduce(collection, func, accumulator){
_.each(collection, function(nextElement){
accumulator = func(accumulator, nextElement)
}
return accumulator
}
Nice! The function _.reduce takes (func in this example) will always take two arguments, the accumulator and the nextElement. Notice, we are not defining nextElement. It is the argument of the anonymous function.
So for *each* ;) element in the collection, we need to call func on the accumulator and the element. As the accumulator changes, it will be overwritten.
But wait. That _.reduce function doesn’t account for when the accumulator is undefined. And as we all know, if the accumulator is undefined, Javascript’s reduce function takes the first element in the collection and uses that as the accumulator. Let’s rewrite our _.reduce function to account for this!
_.reduce = function(collection, iterator, accumulator) {
if (accumulator === undefined){
accumulator = collection[0]
collection = Array.prototype.slice.call(collection, 1)
_.each(collection, function(nextElement){
accumulator = iterator(accumulator, nextElement)
})
} else {
_.each(collection, function(nextElement){
accumulator = iterator(accumulator, nextElement)
})
}
return accumulator
};
Ah, now all our specs are passing! First, we checked if accumulator was undefined. If so, we set accumulator to the first element in the collection. We used the Array.prototype.slice.call() method to reassign the collection with with first element deleted. Everything else stays the same as above.
I recommend memorizing Array.prototype.slice.call(array, 1) — it is a great way to delete the first element of your array! Click here for a refresher on how call, apply and bind work in Javascript.
We are all warmed up now! Let’s keep diving into more methods.