DailyJS

Code Review: Underscore

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

functional code-review

Code Review: Underscore

Posted by Alex R. Young on .
Featured

functional code-review

Code Review: Underscore

Posted by Alex R. Young on .
*Code Review* is a series on DailyJS where I take a look at an open source project to see how it's built. Along the way we'll learn patterns and techniques by JavaScript masters. If you're looking for tips to write better apps, or just want to see how they're structured in established projects, then this is the tutorial series for you.

Underscore provides functional programming tools for JavaScript. Let's take a look inside to
see how key functions are implemented.

About Underscore

Underscore (GitHub: documentcloud / underscore,
License, npm: underscore) from DocumentCloud provides a rich set of functions
that help when dealing with arrays and objects. Many of these functions
are available in other languages, and some can even be found natively in
JavaScript depending on the browser or interpreter.

Underscore's API is accessed through an object rather than changing
Array or Object. That means it's easy to drop
into a project without affecting existing code.

Usage

The canonical example of Underscore is the each function:

_.each([1, 2, 3], function(num) {
  alert(num);
});

The map function is similar, but allows the list to be
changed:

_.map([1, 2, 3], function(num) {
  return num * 3;
});

// Returns [3, 6, 9]

I really like reduce:

_.reduce([1, 2, 3], function(memo, num) { return memo + num; }, 0);
// Returns 6

Underscore quickly became popular for client-side programming, but an
increasing number of Node packages depend on it.

Structure

Like most JavaScript modules, Underscore is distributed in an anonymous
wrapper:

(function() {
  // Establish the root object, `window` in the browser, or `global` on the server.
  var root = this;

  // Save the previous value of the `_` variable.
  var previousUnderscore = root._;

  // Establish the object that gets returned to break out of a loop iteration.
  var breaker = {};

  // Save bytes in the minified (but not gzipped) version:
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  // ... snip
})();

The author (Jeremy Ashkenas) has written detailed comments through the
project.

Underscore will attempt to use native JavaScript methods where
available. We see this intention straight away at around line 35:

// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
  nativeForEach      = ArrayProto.forEach,
  nativeMap          = ArrayProto.map,
  nativeReduce       = ArrayProto.reduce,
  nativeReduceRight  = ArrayProto.reduceRight,
  nativeFilter       = ArrayProto.filter,
  nativeEvery        = ArrayProto.every,
  nativeSome         = ArrayProto.some,
  nativeIndexOf      = ArrayProto.indexOf,
  nativeLastIndexOf  = ArrayProto.lastIndexOf,
  nativeIsArray      = Array.isArray,
  nativeKeys         = Object.keys,
  nativeBind         = FuncProto.bind;

The most important function in Underscore is each, because
so many other methods are built on it. Because it delegates to
ECMAScript 5's native forEach it has to be compatible. It's
also defined in the each variable, which makes using it
throughout the rest of the file more straightforward (and save bytes):

var each = _.each = _.forEach = function(obj, iterator, context) {
  if (obj == null) return;
  if (nativeForEach && obj.forEach === nativeForEach) {
    obj.forEach(iterator, context);
  } else if (_.isNumber(obj.length)) {
    for (var i = 0, l = obj.length; i < l; i++) {
      if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
    }
  } else {
    for (var key in obj) {
      if (hasOwnProperty.call(obj, key)) {
        if (iterator.call(context, obj[key], key, obj) === breaker) return;
      }
    }
  }
};

A guard against null values is used, then
nativeForEach is tested. If this isn't available and the
object looks like an array, a simple for loop is used to iterate over
each value and run the callback. If the object looks like an
Object, a for... in loop is used instead.

However, because the nativeForEach && obj.forEach ===
nativeForEach
test is used, nativeForEach will only
ever be used for arrays. The Mozilla Developer Network has some guidance
on implementing forEach-compatible methods, in the Array
forEach
documentation
:

if (!Array.prototype.forEach)
{
  Array.prototype.forEach = function(fun /*, thisp */)
  {
    "use strict";

    if (this === void 0 || this === null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in t)
        fun.call(thisp, t[i], i, t);
    }
  };
}

Since forEach is defined on Array.prototype,
Underscore has to provide its own functionality for objects. That means
this will work as expected:

_.each({ a: '1', b: '2' }, function(a, b) { console.log(a, b) });

Building on each

The map function works in a very similar way, testing for
native support then calling each if required:

_.map = function(obj, iterator, context) {
  var results = [];
  if (obj == null) return results;
  if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
  each(obj, function(value, index, list) {
    results[results.length] = iterator.call(context, value, index, list);
  });
  return results;
};

If the native method can be used, the results are returned right away.
Otherwise, the results are collected up and returned by using
each.

The context parameter is used throughout Underscore. This
is used by call to bind the callback. The first parameter
of call is thisArg

If thisArg is null or undefined, this will be the global object. Otherwise, this will be equal to Object(thisArg)

The MDN documentation on
call

has more information and examples.

Tests

The tests are written with QUnit, so
they can be run in a browser very easily: Underscore
tests
.

Conclusion

Underscore's source reads almost like JavaScript documentation for the
methods it implements. And it's nice to see that a library that can give
so many productivity gains is actually simple and easy to follow.

Do you fancy building your own functional programming library? Start
with each and see how far you can get!