DeepFindWhere Mixin For Underscore.js

by Andrew on October 24, 2015

Today, I’ll show you how you can extend Underscore.js to easily perform a deep comparison between each element in a collection against a source object, returning the first element that has equivalent property values.

Underscore has a convenient _.mixin method which we’ll use to extend the library.

_.mixin({
  hasEqual: function(obj1, obj2, keys) {
    if (_.isUndefined(keys)) {
      keys = _.keys(obj2);
    }
    var cmp1 = _.pick(obj1, keys);
    var cmp2 = _.pick(obj2, keys);
    return _.isEqual(cmp1, cmp2);
  },
  deepFindWhere: function(cmp, attrs) {
    var result;

    if (_.hasEqual(cmp, attrs)) {
      return cmp;
    }

    for (var i in cmp) {
      if (cmp.hasOwnProperty(i)) {
        if (_.isArray(cmp[i])) {
          _.each(cmp[i], function(obj) {
            if (result) {
              return;
            }
            result = _.deepFindWhere(obj, attrs);
          });
        } else if (_.isObject(cmp[i])) {
          result = _.deepFindWhere(cmp[i], attrs);
        }
        if (result) {
          return result;
        }
      }
    }
  }
});

As you can see, we’ve added two new functions:

_.hasEqual

Accepts as arguments two objects followed by an array of strings to be used as a subset of keys for comparison.

If all values in the subset are equal across the two objects, the function returns true.
If any of the values do not match, it returns false.

// Example
var obj1 = {name: 'Andrew', city: 'Los Angeles'};
var obj2 = {name: 'David', city: 'Los Angeles'};
_.hasEqual(obj1, obj2, ['city']) // returns true
_.hasEqual(obj1, obj2, ['name']) // returns false

I made _.hasEqual a separate function because it’s necessary for _.deepFindWhere, but also useful in it’s own right.

Note: If a third argument is not passed, it is assumed that the second object’s keys are the subset to be used for comparison.

_.deepFindWhere

A recursive function which searches a collection until it finds a match. The collection being searched can contain properties which are objects or arrays. The same goes for any nested objects.

// Example
var cmp = {
name: 'Sparky',
  dogs: [
    {
      name: 'Spot'
    },
    {
      name: 'Rover',
      dogs: [
        {
          name: 'Cody'
        }
       ]
     }
   ]
 };
 var attrs = { name: 'Rover' };
 _.deepFindWhere(cmp, attrs) // returns { name: 'Rover', dogs: [{ name: 'Cody' }] };

{ 0 comments }

Two-Way Model Bindings in Backbone

January 8, 2013

Lately, I’ve been doing a lot of work with Backbone.js. As client-side MV*/MVWs (Model-View-Whatever) continue to grow in popularity, there’s an ongoing debate over which one is best. I like Backbone. I’m comfortable using it. However, a coworker recently pointed me to Ember.js for their stellar implementation of two-way data binding. I haven’t used Ember [...]

Read the full article →

How To: Detect Backbone Memory Leaks

November 4, 2012

If you have a desire to create sophisticated client-side Web apps, Backbone.js is an awesome place to start. With client-side apps, a big area for concern is memory management. As front-end developers, memory management might not be something we’re all used to worrying about – however, when full page refreshes are few and far between, [...]

Read the full article →