The bind
and bindAll
functions are just the tip of the iceberg of what Underscore has to offer web developers. While a full explanation of everything that Underscore has to offer is outside the scope of this book, it is worth taking a few moments to examine a few of Underscore's most useful functions. If you want to learn more about Underscore beyond what we have covered in this chapter, I strongly recommend that you read its web page (http://underscorejs.org/), which has well-written documentation for every method in the library.
Every JavaScript developer knows how to iterate using the for
loops, but Underscore provides three powerful alternatives to those native loops: each
, map
, and reduce
. While all three of these methods (along with many of the other Underscore methods) are included natively in ES5-supporting browsers, older browsers, unfortunately, do not have support for them. These alternative loops are so convenient that you may find yourself never using the native for
loop again. Thankfully, Underscore provides its version of them, allowing you to bridge the gap until all major browsers support ES5.
Let's start with an example of the usage of each:
var mythicalAnimals = ['Unicorn', 'Dragon', 'Honest Politician']; _.each(mythicalAnimals, function(animalName, index) { alert('Animal #' + index + ' is ' + animalName); });
This will be the equivalent of the preceding code using JavaScript's native for
loop:
var mythicalAnimals = ['Unicorn', 'Dragon', 'Honest Politician']; for (var index = 0; index < mythicalAnimals.length; index++) { var animalName = mythicAnimals[index]; alert('Animal #' + index + ' is ' + animalName); }
Alternatively, if we instead use the for
/in
syntax:
var mythicalAnimals = ['Unicorn', 'Dragon', 'Honest Politician']; for (var index in mythicalAnimals) { var animalName = mythicalAnimals[index]; alert('Animal #' + index + ' is ' + animalName); }
As you can see, both native implementations require you to extract the value (in this case, animalName
) inside the loop, whereas Underscore's version provides it automatically.
Underscore's map
and reduce
methods are even more powerful. The map
method allows you to convert an array of variables into another (different) array of variables, while reduce converts an array of variables into a single variable. For instance, let's say you've used jQuery's text
method to extract a bunch of numbers, but because they came from the DOM, those numbers are actually strings (in other words, 5
instead of 5
). By using map, which has an almost identical syntax as each, you can easily convert all those strings into actual numbers, as follows:
var stringNumbers = ["5", "10", "15"]; var BASE = 10; // when we parse strings in to numbers in // JavaScript we have to specify which base to use var actualNumbers = _.map(stringNumbers, function(numberString, index) { return parseInt(numberString, BASE); }); // actualNumbers == [5, 10, 15]
As you can see from the preceding example, the values returned inside the map
function are added to the array returned by the overall map
operation. This lets you convert any kind of array into another kind of array, as long as you can define a function that sits in the middle and performs the desired conversion.
However, what if you wanted to combine or sum up all those numbers? In that case, you'd instead want to use reduce
, as shown here:
var total = _.reduce(actualNumbers, function(total, actualNumber) { return total + actualNumber; }, 0); // total == 30
The reduce
function is slightly more complex than the previous functions because of its last argument, which is known as the memo argument. This argument serves as the starting value for the object that reduce will eventually return, and then as each value is iterated through that, memo is replaced by whatever value the reduce
function (the function that is passed in to reduce) returns. The reduce
function is also passed the previous memo as its first argument, so each iteration can choose to modify the previous value the way it wants to. This allows reduce to be used for far more complex operations than to simply add two numbers together.
Another common operation that is made easier with Underscore is copying the properties of one object onto another, which is solved with the Underscore methods extend
(not to be confused with Backbone's extend
class) and defaults
. For instance, let's say you are using a third-party widget, such as a jQuery UI component, that takes a configuration argument when it is created. You might want to keep some configuration options the same for every widget in your site, but at the same time, you might want certain widgets to also have their own unique options.
Let's imagine that you've defined these two sets of configurations with two objects, one for the common configuration and one for a specific widget's configuration:
var commonConfiguration = {foo: true, bar: true}; var specificConfiguration = {foo: false, baz: 7};
The extend
method takes the first argument given to it and copies the properties from each successive argument onto it. In this case, it allows you to take a new object, apply the common options to it first, and then apply the specific options, as follows:
var combined = _.extend({}, commonConfiguration, specificConfiguration); // combined == {foo: false, bar: true, baz: 7}
The defaults
method works in a similar manner, except that it only copies over values that the object doesn't already have. We can instead rewrite the preceding example with defaults
, simply by changing the argument order:
var combined = _.defaults({}, specificConfiguration , commonConfiguration); // combined == {foo: false, bar: true, baz: 7}
As its name implies, the defaults
method is very handy when you want to specify default values for an object but don't want to replace any values you've already specified.
Two of the most common uses of map
involve getting certain properties from an array of objects or calling a certain method on every object in an array. Underscore provides additional convenience methods to do exactly this: pluck
and invoke
.
The pluck
method allows you to extract a single property value from each member of an array. For example, let's say we have an array of objects representing fake books, as follows:
var fakeBooks = [ {title: 'Become English Better At', pages: 50, author: 'Bob'}, {title: 'You Is Become Even Better at English', pages: 100, author: 'Bob'}, {title: 'Bob is a Terrible Author', pages: 200, author: 'Fred the Critic'} ];
If you want to create a list of just the titles of these books, you can use the pluck
method as shown here:
var fakeTitles = _.pluck(fakeBooks, 'title'),// fakeTitles == ['Become English Better At', ...]
The invoke
method works in a similar way as pluck
method, except that it assumes that the the provided property is a method and runs it and then adds the result to the returned array. This can be best demonstrated with the following example:
var Book = Backbone.Model.extend({ getAuthor: function() { // the "get" method returns an attribute of a Model; // we'll learn more about it in the following chapter return this.get('author'), } }); var books = [new Book(fakeBooks[0]), new Book(fakeBooks[1]), new Book(fakeBooks[2])]; var authors = _.invoke(books, 'getAuthor'), // == ['Bob', 'Bob', 'Fred the Critic']
Underscore also has many other useful functions, and as we have mentioned before, all of them are well documented at http://underscorejs.org/. Since every Backbone programmer is guaranteed to have Underscore available, there's no reason why you shouldn't take advantage of this great library; even spending just a few minutes on the Underscore site will help make you aware of everything that it has to offer.
35.170.81.33