Underscore.js

for President.

WAT.


"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."

Basically:

A really awesome utility belt.
"Namespaced" under _
That's an underscore.

Let's jump into it


Some code!

max / min

var someNumbers = [15, 88, 32, 64, 48, 16, 8];console.log(_.max(someNumbers)) // 88
... or with an array of objects:
var coders = [    { nick: 'HooverDam', age: 27 },    { nick: 'tjoc', age: 88 },    { nick: 'Voldern', age: 16 }];console.log(_.max(coders, function(coder) {    return coder.age;}); // { nick: 'tjoc', age: 88 }

groupBy

var employees = [
    { name: 'Espen H', role: 'Coder' },
    { name: 'Hanne', role: 'Project leader' },
    { name: 'André', role: 'Coder' },
    { name: 'Audun', role: 'Sysop' },
    { name: 'Trygve', role: 'Sysop' },
    { name: 'Naveed', role: 'Coder' },
    { name: 'Espen V', role: 'Coder' },
    { name: 'Tommy', role: 'Suit' },
    { name: 'Stein', role: 'Sysop' },
    { name: 'Christer', role: 'Coder' }
];
_.groupBy(employees, 'role');

/* {
    'Coder': [{}, {}, ...],
    'Project leader': [{ name: 'Hanne', role: 'Project leader' }],
    'Sysop': [{}, {}, ...],
    'Suit': [{ name: 'Tommy', role: 'Suit'}]
} */

filter

var employees = [
    { name: 'Espen H', role: 'Coder' },
    { name: 'Hanne', role: 'Project leader' },
    { name: 'André', role: 'Coder' },
    ...
    { name: 'Tommy', role: 'Suit' },
    { name: 'Stein', role: 'Sysop' },
    { name: 'Christer', role: 'Coder' }
];
_.filter(employees, function(employee) {
    return employee.role == 'suit';
});

/* [{ name: 'Tommy', role: 'Suit'}] */

find

Same as filter, but returns first matching:
var employees = [
    { name: 'Espen H', role: 'Coder' },
    { name: 'Hanne', role: 'Project leader' },
    { name: 'André', role: 'Coder' },
    ...
    { name: 'Tommy', role: 'Suit' },
    { name: 'Stein', role: 'Sysop' },
    { name: 'Christer', role: 'Coder' }
];

_.find(employees, function(employee) {
    return employee.role === 'Coder'
});
// { name: 'Espen H', role: 'Coder' }

contains

var suit      = { name: 'Tommy', role: 'Suit' }
  , coder     = { name: 'André', role: 'Coder' }
  , employees = [suit, coder];

_.contains(employees, suit); // true
However: references vs copies
var suit = { name: 'Tommy', role: 'Suit' };
var employees = [
    { name: 'André', role: 'Coder' },
    { name: 'Tommy', role: 'Suit' }
];

_.contains(employees, suit); // false
Works on objects too! (for values)

pluck

var employees = [
    { id: 1337, name: 'Espen H', role: 'Coder' },
    { id: 1338, name: 'Hanne', role: 'Project leader' },
    { id: 31337, name: 'André', role: 'Coder' },
    { id: 1341, name: 'Christer', role: 'Coder' }
];

_.pluck(employees, 'id'); // [1337, 1338, 31337, 1341]

shuffle

var numbers = [
    1, 2, 4, 8, 16,
    32, 64, 128, 256
];

_.shuffle(numbers); // [16, 1, 32, 4, 2, 8, 128, 256, 64]

compact


var undef, values = [
    1337,
    false,
    null,
    '',
    undef,
    parseInt('z'),
    15
];
_.compact(values); // [1337, 15]

difference

var someNumbers = [8, 16, 32, 128];
var someOthers  = [2, 4, 8, 16, 256, 512];

_.difference(someNumbers, someOthers); // [32, 128]

Note: Elements from first not present in subsequent

without

var suit = { name: 'Tommy', role: 'Suit' };
var employees = [
    { name: 'Hooverdam', role: 'Coder' },
    suit,
    { name: 'André', role: 'Coder' }
];

var suitless = _.without(employees, suit);
/* [
    { name: 'Hooverdam', role: 'Coder' },
    { name: 'André', role: 'Coder' }
] */

uniq

var someNumbers = [8, 16, 32, 128];
var someOthers  = [2, 4, 8, 16, 256, 512];

_.uniq(someNumbers, someOthers); // [8, 16, 32, 128];
Don't get bitten by object reference
var employees = [ { name: 'Tommy' }, { name: 'Espen' }, { name: 'André } ];
var señors = [ { name: 'André' } ];
_.uniq(employees, señors, function(person) { return person.name; });
_.uniq(employees, señors, JSON.stringify);

object

Array(s) => object
var roles     = ['SysOp', 'Coder', 'Coder', 'Suit']
  , employees = ['Roy', 'André', 'Christer', 'Tommy'];

_.object(roles, employees);

/* {
  SysOp: 'Roy',
  Coder: 'Christer',
  Suit: 'Tommy'
} */
Last value overwrites first, on duplicates

bind

Better name than $.proxy ;-)
var someClass = {
    bindInput: function() {
        var boundHandler = _.bind(this.onInput, this);
        $('input[type=number]').on('input', boundHandler);
    },
    
    onInput: function(e) {
        if (e.target.valueAsNumber > 1337) {
            this.onLargerThanLeet();
        }
    },
    
    onLargerThanLeet: function() {
        window.alert('WE ARE LARGER THAN LEET');
    }
};

bindAll

Same as bind, but binds multiple
var buttonView = {
    label   : 'underscore',
    onClick : function() { alert('clicked: ' + this.label); },
    onHover : function() { console.log('hovering: ' + this.label); }
};

_.bindAll(buttonView, 'onClick', 'onHover');

$('.underscore-bind').on({
    click: buttonView.onClick,
    hover: buttonView.onHover
});

memoize

var md5page = function() {
    return crypto.md5($('html').html());
};

var cachedMd5Page = _.memoize(md5page);

cachedMd5Page(); // 1 sec
cachedMd5Page(); // 0.000001 sec

defer

Defer invoking of function until call stack is clear
var xhrCache = {},
getFile = function(url, onComplete) {
    if (xhrCache[url]) {
        return _.defer(onComplete, xhrCache[url]);
    }
    
    $.xhr(url, function(res) {
        xhrCache[url] = res;
        onComplete(res);
    });
});

throttle

var onScroll = function() {
    console.log('We\'re at ' + window.scrollTop + 'px');
};

$(window).on('scroll', _.throttle(onScroll, 50));
See also: debounce

once

var initialize = _.once(function() {
    $('.some-el').on('hover', someFunction);
});

initialize(); // Binds event
initialize(); // Does nothing

after

var responses = [];

var onAllComplete = _.after(2, function() {
    // Combine responses somehow
});

var onComplete = function(res) {
    responses.push(res);
    
    onAllComplete();
};

$.xhr('twitter.com/vgtech', onComplete);
$.xhr('twitter.com/vgnett', onComplete);

keys / values

var employeesAge = {
    'Hooverdam': 27,
    'Voldern'  : 16,
    'tjoc'     : 87,
};

_.keys(employeesAge);   // ['Hooverdam', 'Voldern', 'tjoc']
_.values(employeesAge); // [27, 16, 87]

functions


_.functions(jQuery.fn); // Whole lotta functions.

defaults


var options = {
    'token' : 'someToken',
    'format': 'json'
};

_.defaults(options, {
    'baseUrl' : 'http://www.vg.no/api',
    'format'  : 'xml'
});

/* options = {
    "token": "someToken",
    "format": "json",
    "baseUrl": "http://www.vg.no/api"
} */

omit

var apiResponse = {
    id: 1337,
    title: 'VG wins award for most beautiful webpage on the interwebs',
    description: 'Some really verbose description here that we could live without',
    created: 1376560776,
    _meta: {
        commentsUrl: '...',
        photoUrl: '...'
    }
};

_.omit(apiResponse, [
    '_meta',
    'description'
]);

has

Object.prototype.debug = function() {
    return JSON.stringify(this);
};

var myObj = { 'foo': 'bar' };
!!myObj.foo;  // true
!!myObj.debug // true

_.has(myObj, 'foo');   // true
_.has(myObj, 'debug'); // false

isEqual


var tjoc = { name: 'Tommy', role: 'Suit' };
var suit = { name: 'Tommy', role: 'Suit' };

tjoc == suit;          // false
_.isEqual(tjoc, suit); // true

random


/* _.random(min, max); */

_.random(13, 24); // 16


Lodash!


  • Drop-in replacement
  • Faster
  • Better tested
  • AMD + CJS compatible
  • Use it!
  • Require:
    • paths: { 'underscore': 'path/to/lodash' }

Read the docs!

Tons of awesome stuff.

Underscore for President

By Espen Hovlandsdal

Underscore for President

  • 1,439