Recently I held a short lecture on the use of functions in Javascript, and how it’s possible to use functionality like map, reduce, filter and Javascript’s amazing variable context. I prefer to use underscore.js so that I can use the wonderful functionality even on old browsers.
Lets start easy with _.map. Map is easy, what it does is take an input array, send each element into a function, and putting the result in a result array. The resulting array contains mapped versions of each element in the input array.
var inputArray = ['apple', 'banana', 'pineapple']; var resultArray = []; for(var i=0;i<inputArray.length;i++){ resultArray.push(inputArray[i] + 'man'); }
This would be equal to the following map statement:
var inputArray = ['apple', 'banana', 'pineapple']; var resultArray = _.map(inputArray, function(fruit){ return fruit + 'man'; });
Easy, ha? You see how it is a lot more easy to read (and hence maintain) as there are fewer places for bugs.
Sometimes inline functions are good to use, but most of the time I prefer using named functions for code clarity.
In javascript you can treat functions just as a variable, creating them using the function operator. Here we need a function that takes one parameter, so that we can use it with _.map. Lets see what that can look like:
var createFruitMan = function(fruit){ return fruit + 'man'; }; var inputArray = ['apple', 'banana', 'pineapple']; var resultArray = _.map(inputArray, createFruitMan);
That looks pretty good, right?
Now, lets jump to reduce. You use reduce to iterate over an array of objects, calling the same function for each object, with the object and a memory variable as in parameter. The returned value will be used as the memory parameter in the subsequent function call, and the result of the whole function is the last elements returned value.
Lets say we want to glue some string elements in an array together. With a traditional for loop we could do like this:
var inputArray = ['apple', 'banana', 'pineapple']; var result = ""; for(var i=0;i<inputArray.length;i++){ result += (result.length?", ":"") + inputArray[i]; }
A for loop almost never look clean. With reduce it would look like this:
var inputArray = ['apple', 'banana', 'pineapple']; var result = _.reduce(inputArray, function(memory, element){ return memory + (memory.length?", ":"") + element; }, "");
The _.reduce takes three parameters, an input array to reduce, a function to call for each array element, and a start value for the memory element. The function will be called for each element, and the returned value will then used as the memory parameter in the next function call, or returned as result for the whole reduce call.
Lets move the function outside.
var glueString = function(element, elementToGlue){ return element + (element.length?", ":"") + elementToGlue; }; var inputArray = ['apple', 'banana', 'pineapple']; var result = _.reduce(inputArray, glueString, "");
Ah, what the heck, lets play some more:
var glueString = function(glue){ return function(element, elementToGlue){ return element + (element.length?glue:"") + elementToGlue; } }; var implode = function(glue, pieces){ return _.reduce(inputArray, glueString(glue), ""); } var inputArray = ['apple', 'banana', 'pineapple']; var res = implode(", ", inputArray);
Now this is a little bit of Javascript function context magic.
The function glueString takes one parameter, glue, and returns a new string that takes two parameters. The context of a javascript function is that of where it is created, so here the returned function will have access to a variable glue.
Of course, putting the function within another function named implode (as the PHP function) is hard to resist.
And this, the access to variables in the functions context, is very powerful.
You really can’t talk about underscore.js without mentioning each. Each is very straight forward. It takes an array and a function, calls the function for each element in the list, and does nothing more. Like map but without caring about the return value of each function call.
// Now why do something as horrible like this? var arr = []; i = 255; while(i--) arr.push(i); } _.each(arr, alert);
Reduce and Map are the two most important functions from underscore.js. There are loads of other functions, but most of them are just variations on the subject.
Find, filter, reject, every, some, contains, pluck, max etc are more or less variations of reduce and map.
var find = function(list, testFunction){ return _.reduce(list, function(memory, element){ if(testFunction(element)) return element; else return memory; }); }; var filter = function(list, testFunction){ return _.reduce(list, function(memory, element){ if(testFunction(element)) memory.push(element); return element; },[]); }; var every = function(list, testFunction){ return _.reduce(list, function(memory, element){ return memory && testFunction(element); }, true); }; var max = function(list){ return _.reduce(list, function(memory, element){ return element>memory?element:memory; }, list[0]); };
As you can see, the _.reduce is incredibly powerful, and it’s easy to do magic with reduce and map.
In the beginning of this post I said that I used underscore for its backward compatibility. For Firefox, Chrome and IE9+ map, reduce and most of underscore are part of the array object, so if you don’t care about ie6, ie7 and ie8, just ignore the _. and write
[1,2,3].map(function(e){return e*2});
It’s a wonderful world! Be sure to read the docs over at underscore.js, it’s really easy to work with it.