Written by Darren Torpey and presented to Rue La La in May 2013.
This guide was written based upon Underscore.js version 1.4.4.
From the official website:
Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects.
It effectively extends the "standard lib" for JavaScript, allowing you to easily manipulate data, query the logical type and size of objects, get the keys from objects, etc. It also allows for robust, idiomatic, functional-programming-inspired code.
A few examples...
// Want an object's keys, but not the attributes of its parent/ancestor prototypes?
Object.keys(obj) // Oops; won't work in IE < 9. Gotcha!
_.keys(obj) // All good, all the time. +1 FTW
// For example:
var developer = { name: 'Darren', age: 31, role: 'Developer' };
_.keys(developer) // =>['name', 'age', 'role']
// _.each on an array
_.each(items, function(item) {
item.initialize();
item.prepareForTotalAwesomeness();
});
// _.each on an object
var buttonMap = { R: 'right', L: 'left', U: 'up', D: 'down' };
_.each(buttonMap, function(direction, button) {
console.log('Press ' + button + ' button to move "' + direction + '"');
});
/*
Logs:
Press R button to move "right"
Press L button to move "left"
Press U button to move "up"
Press D button to move "down"
*/
Note: Delegates to ECMAScript 5's native forEach
if available.
_.map
_.map([{ id: 1, name: 'Alex' }, { id: 2, name: 'Darren' }, { id: 3, name: 'Audrey' }], function(developer) {
return developer.name;
}
// => ['Alex', 'Darren', 'Audrey']
_.pluck
_.pluck([{ id: 1, name: 'Alex' }, { id: 2, name: 'Darren' }, { id: 3, name: 'Audrey' }], 'name')
// => ['Alex', 'Darren', 'Audrey'];
_.reduce
_.reduce([1, 2, 3, 4], function(sum, int) {
return sum + int;
}, 0);
// => 10
_.keys
_.keys({ name: 'Darren', age: 31, role: 'Developer' })
// => ['name', 'age', 'role']
_.pick
_.pick({ name : 'Darren', age: 31, role : 'Developer' }, 'name', 'role')
// => { name : 'Darren', role : 'Developer' }
_.omit
_.omit({ name : 'Darren', age: 31, role : 'Developer' }, 'age')
// => { name : 'Darren', role : 'Developer' }
_.invert
_.invert({ right: 'R', left: 'L', up: 'U', down: 'D' })
// => { R: 'right', L: 'left', U: 'up', D: 'down' }
_.pairs
Generates an array of key/value pair arrays for each key/value pair in the object
_.pairs({ name: 'Darren', role: 'Developer', home: 'Cambridge' })
// => [['name', 'Darren'], ['role', 'Developer'], ['home', 'Cambridge']]
_.object
Generates an array of objects from the keys and values given
_.object(['Moe', 'Larry', 'Curly'], [30, 40, 50])
// => { Moe: 30, Larry: 40, Curly: 50 }
_.object([['Moe', 30], ['Larry', 40], ['Curly', 50]])
// => { Moe: 30, Larry: 40, Curly: 50 }
_.extend
_.extend({ kind: true }, { honest: true, kind: false }, { honest: false })
// => { kind: false, honest: false }
_.extend(
{ repeat: true, volume: 1 },
{ volume: 2, repeat: false },
{ volume: 4, mutable: true }
)
// => { repeat: false, volume: 4, mutable: true }
_.defaults
_.defaults({ kind: true }, { honest: true, kind: false }, { honest: false })
// => { kind: true, honest: true }
_.defaults(
{ repeat: true, volume: 1 },
{ volume: 2, repeat: false },
{ volume: 4, mutable: true }
)
// => { repeat: true, volume: 1, mutable: true }
_.contains
_.contains([1, 2, 3, 4], 2); // true
_.contains([1, 2, 3, 4], 5); // false
_.all
a.k.a every
_.every([1, 2, 3, 4], function(int) { return int > 3 }); // false
_.every([1, 2, 3, 4], function(int) { return int < 5 }); // true
_.any
a.k.a some
_.any([1, 2, 3, 4], function(int) { return int > 3 }); // true
_.any([1, 2, 3, 4], function(int) { return int < 5 }); // true
_.any([1, 2, 3, 4], function(int) { return int > 4 }); // false
Note: a significant benefit of this function is that it stops checking if/when it finds one that returns true
. This cab be a significant efficiency gain when you're dealing with heavy operations for each check.
Here's an example, slightly modified from our code base:
// The "select" operation here is expensive, so it's good that
// we stop checking the other attributes as soon as one is
// found to have an empty result set
var disableBasedOnSubsetQuery = _.any(attributes, function(attribute) {
results = self.select(_.omit(choices, attribute));
return 0 === results.length;
});
_.without
_.without([1, 2, 1, 0, 3, 1, 4], 0, 1)
// => [2, 3, 4]
_.union
_.union([1, 2, 3], [101, 2, 1, 10], [2, 1, 5])
// => [1, 2, 3, 101, 10, 5]
_.union([1, 2, 3], [101, 2, 1, 101, 10], [2, 5, 1, 5])
// => [1, 2, 3, 101, 10, 5]
_.intersection
_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1])
// => [1, 2]
_.difference
_.difference([1, 2, 3, 4, 5], [5, 2, 10])
// => [1, 3, 4]
_.uniq
_.uniq([1, 2, 1, 3, 1, 4])
// => [1, 2, 3, 4]
_.size
_.size([]); // 0
_.size({}); // 0
_.size({ propOne: 'one', propTwo: 'two' }); // 2
_.size([1, 2, 3]); // 3
_.isEmpty
_.isEmpty({}) // true
_.isEmpty([]) // true
_.isEmpty('') // true
_.isEmpty({ key: 'value' }); // false
_.isEmpty(' '); // false
_.isEmpty([1, 2, 3]) // false
_.isEmpty(['']) // false
_.isEmpty([{}]) // false
Other is
functions, mostly self-explanatory
isElement | isArray | isObject | isArguments | isFunction | isString | isNumber | isBoolean | isDate
Careful, though:
// An array is an object, too!
_.isObject({}) // true
_.isObject([]) // true <-- don't forget this
_.isArray([]) // true
_.isArray({}) // false
Guideline: always try it out in a JS console first to know for sure how the method works! The source is easy to read, too -- especially the annotated source code.
_.range
_.range(1, 11); // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
_.invoke
Calls the specified function on each item in the given collection
var people = [
{
id: 11,
name: 'Darren',
getTagText: function() { return this.name + ' - ' + this.id }
},
{
id: 2,
name: 'Alex',
getTagText: function() { return this.name + ' - ' + this.id; }
}
];
_.invoke(people, 'getTagText');
// ['Darren - 11', 'Alex - 2']
_.identity
Useful for other methods like _.sortBy that require you to give a comparison function but where you sometimes just want elements to be compared directly to one another. See the _.sortBy
example below.
_.sortBy
_.sortBy([1, 204, 3, 10, 99, 2031], _.identity)
// => [1, 3, 10, 99, 204, 2031]
_.sortBy([{ id: 2, name: 'Darren' }, { id: 1, name: 'Alex' }, { id: 3, name: 'Audrey' }], 'name')
// => [{ id: 1, name: 'Alex' }, { id: 3, name: 'Audrey' }, { id: 2, name: 'Darren' }]
_.groupBy
var widgets = [{ id: 1, tag: 'A' }, { id: 2, tag: 'B' }, { id: 3, tag: 'A' }, { id: 4, tag: 'B' }, { id: 5, tag: 'A' }];
_.groupBy(widgets, 'tag')
/*
=>
{
"A": [
{
"id": 1,
"tag": "A"
}, {
"id": 3,
"tag": "A"
}, {
"id": 5,
"tag": "A"
}
],
"B": [
{
"id": 2,
"tag": "B"
}, {
"id": 4,
"tag": "B"
}
]
}
*/
Often times the data manipulation operation you need to perform can be expressed as a composition of the simple, idiomatic operations that underscore's functions help you with.
In these cases, _.chain can help you write this code in an idiomatic, easy-to-read fashion.
Take this example:
// This is rather awkward and unclear...
var commaListAwk = function(array) {
return _.uniq(
_.flatten(array)
)
.join(', ');
}
commaListAwk([[1, 2], [1, 3, [5, 11]], [101, 2, 0], [6, 7]]);
// => '1, 2, 3, 5, 11, 101, 0, 6, 7'
// Much better now with chaining!
var commaListBetter = function (array) {
return _.chain(array)
.flatten()
.uniq()
.value()
.join(', ');
};
commaListBetter([[1, 2], [1, 3, [5, 11]], [101, 2, 0], [6, 7]]);
// => '1, 2, 3, 5, 11, 101, 0, 6, 7'
// Watch how easy it is to now add one more step, where we sort the values
var commaListSorted = function (array) {
return _.chain(array)
.flatten()
.uniq()
.sortBy(_.identity)
.value()
.join(', ');
};
commaListSorted([[1, 2], [1, 3, [5, 11]], [101, 2, 0], [6, 7]]);
// => '0, 1, 2, 3, 5, 6, 7, 11, 101'
Thanks to @dariusk for the initial code example, which I have adapted slightly from his comment on this Gist.
a.k.a fold, accumulate, aggregate, compress, or inject
"...a family of higher-order functions that analyze a recursive data structure and recombine through use of a given combining operation the results of recursively processing its constituent parts, building up a return value."
In other words: It turns a collection into a single aggregate object.
Example:
_.reduce(list, iterator, memo)
// Builds a lookup (dictionary) object for the given array of items based on their "id" property
var items = [{ id: 1, name: 'laptop' }, { id: 2, name: 'book' }, { id: 3, name: 'mouse' }]
_.reduce(items, function(itemLookup, item) {
itemLookup[item.id] = item;
return itemLookup;
}, {});
// {
// 1: { id: 1, name: 'laptop' },
// 2: { id: 2, name: 'book' },
// 3: { id: 3, name: 'mouse' }
// };
Okay:
// This is okay, but we have to read the loop very carefully to have any idea
// of what it's doing. Plus, it's easy for side-effects to appear as a
// result of the loop's work, especially since the loop shares scope with
// the rest of the surrounding code
var items = [{ id: 1, name: 'laptop' }, { id: 2, name: 'book' }, { id: 3, name: 'mouse' }];
var itemLookup = {};
for (var i = 0; items.length; i++) {
var item = items[i];
itemLookup[item.id] = item;
}
Better:
// The usage of _.each here helps us manage scope and promotes the encapsulation of
// the behavior being done each time as a separate method
var items = [{ id: 1, name: 'laptop' }, { id: 2, name: 'book' }, { id: 3, name: 'mouse' }];
var itemLookup = {};
_.each(items, function(item) {
itemLookup[item.id] = item;
});
Good:
// The very use of "reduce" signals to the reader that we're taking a collection
// and turning it into an object of the type demonstrated by the third
// parameter passed to the function call
var items = [{ id: 1, name: 'laptop' }, { id: 2, name: 'book' }, { id: 3, name: 'mouse' }];
_.reduce(items, function(itemLookup, item) {
itemLookup[item.id] = item;
return itemLookup;
}, {});
I put this in its own Gist here.
Generally, the guideline is this: "If it's a purely jQuery-oriented operation -- and especially if it's in a jQuery chain -- use jQuery; otherwise use underscore"
Another way of looking at it: "If it's basically just a utility/convenience function in jQuery, use the underscore equivalent instead."
Examples:
// This is a good place to use jQuery's "each" because it lets us keep chaining
// and it is a purely DOM reading and writing operation
this.find('.sometimes-cool-elements')
.initialize()
.each(function() {
var personalityData = $(this).data('personality');
if (personalityData.isCool) {
$(this).addClass('cool');
}
})
.show()
- Real-worldy-underscore http://codepen.io/darrentorpey/pen/KCtlx
- Underscore advanced examples http://codepen.io/darrentorpey/pen/fCjGp
This is great! I think you should add
_.unique
to your list of set functions. In my experience that is the most commonly used set function after_.contains
.