20 min read

In this article Alex Pop, the author of the book, Learning Underscore.js, we will explore Underscore functionality for collections using more in-depth examples. Some of the more advanced concepts related to Underscore functions such as scope resolution and execution context will be explained.

The topics of the article are as follows:

  • Key Underscore functions revisited
  • Searching and filtering

This article assumes that you are familiar with JavaScript fundamentals such as prototypical inheritance and the built-in data types.

The source code for the examples from this article is hosted online at https://github.com/popalexandruvasile/underscorejs-examples/tree/master/collections, and you can execute the examples using the Cloud9 IDE at the address https://ide.c9.io/alexpop/underscorejs-examples from the collections folder.

(For more resources related to this topic, see here.)

Key Underscore functions – each, map, and reduce

This flexible approach means that some Underscore functions can operate over collections: an Underscore-specific term for arrays, array like objects, and objects (where the collection represents the object properties). We will refer to the elements within these collections as collection items.

By providing functions that operate over object properties Underscore expands JavaScript reflection like capabilities. Reflection is a programming feature for examining the structure of a computer program, especially during program execution.

JavaScript is a dynamic language without static type system support (as of ES6). This makes it convenient to use a technique named duck typing when working with objects that share similar behaviors. Duck typing is a programming technique used in dynamic languages where objects are identified through their structure represented by properties and methods rather than their type (the name of duck typing is derived from the phrase “if it walks like a duck, swims like a duck, and quacks like a duck, then it is a duck”). Underscore itself uses duck typing to assert that an object is an array by checking for a property called length of type Number.

Applying reflection techniques

We will build an example that demonstrates duck typing and reflection techniques through a function that will extract object properties so that they can be persisted to a relational database. Usually relational database stores objects represented as a data row with columns types that map to regular SQL data types.

We will use the _.each() function to iterate over object properties and extract those of type boolean, number, string and Date as they be easily mapped to SQL data type and ignore everything else:

var propertyExtractor = (function() {
"use strict"
return {
   extractStorableProperties: function(source) {
     var storableProperties = {};
     if (!source || source.id !== +source.id) {
       return storableProperties;
     }
     _.each(source, function(value, key) {
       var isDate = typeof value === 'object' && value instanceof
       Date;
       if (isDate || typeof value === 'boolean' || typeof value
       === 'number' || typeof value === 'string') {
         storableProperties[key] = value;
       }
     });
     return storableProperties;
   }
};
}());

You can find the example in the propertyExtractor.js file within the each-with-properties-and-context folder from the source code for this article.

The first highlighted code snippet checks whether the object passed to the extractStorableProperties() function has a property called id that is a number. The + sign converts the id property to a number and the non-identity operator !== compares the result of this conversion with the unconverted original value. The non-identity operator returns true only if the type of the compared objects is different or they are of the same type and have different values.

This was a duck typing technique used by Underscore up until version 1.7 to assert whether it deals with an array-like instance or an object instance in its collections related functions.

Underscore collection related functions operate over array-like objects as they do not strictly check for the built in Array object. These functions can also work with the arguments objects or the HTML DOM NodeList objects.

The last highlighted code snippet is the _.each() function that operates over object properties using an iteration function that receives the property value as its first argument and the property name as the optional second argument. If a property has a null or undefined value it will not appear in the returned object.

The extractStorableProperties() function will return a new object with all the storable properties. The return value is used in the test specifications to assert that, given a sample object, the function behaves as expected:

describe("Given propertyExtractor", function() {
describe("when calling extractStorableProperties()", function()
{
   var storableProperties;
   beforeEach(function() {
     var source = {
       id: 2,
      name: "Blue lamp",
       description: null,
       ui: undefined,
       price: 10,
       purchaseDate: new Date(2014, 10, 1),
       isInUse: true,
     };
     storableProperties =
     propertyExtractor.extractStorableProperties(source);
   });
   it("then the property count should be correct", function() {
     expect(Object.keys(storableProperties).length).toEqual(5);
   });
   it("then the 'price' property should be correct", function() {
     expect(storableProperties.price).toEqual(10);
   });
   it("then the 'description' property should not be defined",
   function() {
     expect(storableProperties.description).toEqual(undefined);
   });
});
});

Notice how we used the propertyExtractor global instance to access the function under test, and then, we used the ES5 function Object.keys to assert that the number of returned properties has the correct size.

In a production ready application, we need to ensure that the global objects names do not clash among other best practices.

You can find the test specification in the spec/propertyExtractorSpec.js file and execute them by browsing the SpecRunner.html file from the example source code folder. There is also an index.html file that will display the results of the example rendered in the browser using the index.js file.

Manipulating the this variable

Many Underscore functions have a similar signature with _.each(list, iteratee, [context]),where the optional context parameter will be used to set the this value for the iteratee function when it is called for each collection item. In JavaScript, the built in this variable will be different depending on the context where it is used.

When the this variable is used in the global scope context, and in a browser environment, it will return the native window object instance. If this is used in a function scope, then the variable will have different values:

  • If the function is an object method or an object constructor, then this will return the current object instance. Here is a short example code for this scenario:
    var item1 = {
    id: 1,
    name: "Item1",
    getInfo: function(){
       return "Object: " + this.id + "-" + this.name;
    }
    };
    console.log(item1.getInfo());
    // -> “Object: 1-Item1”
  • If the function does not belong to an object, then this will be undefined in the JavaScript strict mode. In the non-strict mode, this will return its global scope value.

With a library such as Underscore that favors a functional style, we need to ensure that the functions used as parameters are using the this variable correctly. Let’s assume that you have a function that references this (maybe it was used as an object method) and you want to use it with one of the Underscore functions such as _.each.. You can still use the function as is and provide the desired this value as the context parameter value when calling each.

I have rewritten the previous example function to showcase the use of the context parameter:

var propertyExtractor = (function() {
"use strict";
return {
   extractStorablePropertiesWithThis: function(source) {
     var storableProperties = {};
     if (!source || source.id !== +source.id) {
       return storableProperties;
     }
     _.each(source, function(value, key) {
       var isDate = typeof value === 'object' && value instanceof
       Date;
       if (isDate || typeof value === 'boolean' || typeof value
       === 'number' || typeof value === 'string') {
         this[key] = value;
       }
     },
     storableProperties);
     return storableProperties;
   }
};
}());

The first highlighted snippet shows the use of this, which is typical for an object method. The last highlighted snippet shows the context parameter value that this was set to. The storableProperties value will be passed as this for each iteratee function call. The test specifications for this example are identical with the previous example, and you can find them in the same folder each-with-properties-and-context from the source code for this article.

You can use the optional context parameter in many of the Underscore functions where applicable and is a useful technique when working with functions that rely on a specific this value.

Using map and reduce with object properties

In the previous example, we had some user interface-specific code in the index.js file that was tasked with displaying the results of the propertyExtractor.extractStorableProperties() call in the browser. Let’s pull this functionality in another example and imagine that we need a new function that, given an object, will transform its properties in a format suitable for displaying in a browser by returning an array of formatted text for each property. To achieve this, we will use the Underscore _.map() function over object properties as demonstrated in the next example:

var propertyFormatter = (function() {
"use strict";
return {
   extractPropertiesForDisplayAsArray: function(source) {
     if (!source || source.id !== +source.id) {
       return [];
     }
     return _.map(source, function(value, key) {
       var isDate = typeof value === 'object' && value instanceof
       Date;
       if (isDate || typeof value === 'boolean' || typeof value
       === 'number' || typeof value === 'string') {
         return "Property: " + key + " of type: " + typeof value
         + " has value: " + value;
       }
       return "Property: " + key + " cannot be displayed.";
     });
   }
};
}());

With Underscore, we can write compact and expressive code that manipulates these properties with little effort. The test specifications for the extractPropertiesForDisplayAsArray() function are using Jasmine regular expression matchers to assert the test conditions in the highlighted code snippets from the following example:

describe("Given propertyFormatter", function() {
describe("when calling extractPropertiesForDisplayAsArray()",
function() {
   var propertiesForDisplayAsArray;
   beforeEach(function() {
     var source = {
       id: 2,
       name: "Blue lamp",
       description: null,
       ui: undefined,
       price: 10,
       purchaseDate: new Date(2014, 10, 1),
       isInUse: true,
     };
     propertiesForDisplayAsArray =
     propertyFormatter.extractPropertiesForDisplayAsArray(source);
   });
   it("then the returned property count should be correct",
   function() {
     expect(propertiesForDisplayAsArray.length).toEqual(7);
   });
   it("then the 'price' property should be displayed", function()
   {
     expect(propertiesForDisplayAsArray[4]).toMatch("price.+10");
   });
   it("then the 'description' property should not be displayed",
   function() {
     expect(propertiesForDisplayAsArray[2]).toMatch("cannot be
     displayed");
   });
});
});

The following example shows how _.reduce() is used to manipulate object properties. This will transform the properties of an object in a format suitable for browser display by returning a string value that contains all the properties in a convenient format:

extractPropertiesForDisplayAsString: function(source) {
if (!source || source.id !== +source.id) {
   return [];
}
return _.reduce(source, function(memo, value, key) {
   if (memo && memo !== "") {
     memo += "<br/>";
   }
   var isDate = typeof value === 'object' && value instanceof
   Date;
   if (isDate || typeof value === 'boolean' || typeof value ===
   'number' || typeof value === 'string') {
    return memo + "Property: " + key + " of type: " + typeof
     value + " has value: " + value;
   }
 return memo + "Property: " + key + " cannot be displayed.";
},
"");
}

The example is almost identical with the previous one with the exception of the memo accumulator used to build the returned string value.

The test specifications for the extractPropertiesForDisplayAsString() function are using a regular expression matcher and can be found in the spec/propertyFormatterSpec.js file:

describe("when calling extractPropertiesForDisplayAsString()", function() {
var propertiesForDisplayAsString;
beforeEach(function() {
   var source = {
     id: 2,
     name: "Blue lamp",
     description: null,
     ui: undefined,
     price: 10,
     purchaseDate: new Date(2014, 10, 1),
     isInUse: true,
   };
   propertiesForDisplayAsString =
   propertyFormatter.extractAllPropertiesForDisplay(source);
});
it("then the returned string has expected length", function() {
   expect(propertiesForDisplayAsString.length).toBeGreaterThan(0);
});
it("then the 'price' property should be displayed", function() {
   expect(propertiesForDisplayAsString).toMatch("<br/>Property:
   price of type: number has value: 10<br/>");
});
});

The examples from this subsection can be found within the map.reduce-with-properties folder from the source code for this article.

Searching and filtering

The _.find(list, predicate, [context]) function is part of the Underscore comprehensive functionality for searching and filtering collections represented by object properties and array like objects. We will make a distinction between search and filter functions with the former tasked with finding one item in a collection and the latter tasked with retrieving a subset of the collection (although sometimes, you will find the distinction between these functions thin and blurry).

We will revisit the find function and the other search- and filtering-related functions using an example with slightly more diverse data that is suitable for database persistence. We will use the problem domain of a bicycle rental shop and build an array of bicycle objects with the following structure:

var getBicycles = function() {
return [{
   id: 1,
   name: "A fast bike",
   type: "Road Bike",
   quantity: 10,
   rentPrice: 20,
   dateAdded: new Date(2015, 1, 2)
}, {
...
}, {
   id: 12,
   name: "A clown bike",
   type: "Children Bike",
   quantity: 2,
   rentPrice: 12,
   dateAdded: new Date(2014, 11, 1)
}];
};

Each bicycle object has an id property, and we will use the propertyFormatter object built in the previous section to display the examples results in the browser for your convenience.

The code was shortened here for brevity (you can find its full version alongside the other examples from this section within the searching and filtering folders from the source code for this article). All the examples are covered by tests and these are the recommended starting points if you want to explore them in detail.

Searching

For the first example of this section, we will define a bicycle-related requirement where we need to search for a bicycle of a specific type and with a rental price under a maximum value. Compared to the previous _.find() example, we will start with writing the tests specifications first for the functionality that is yet to be implemented. This is a test-driven development approach where we will define the acceptance criteria for the function under test first followed by the actual implementation. Writing the tests first forces us to think about what the code should do, rather than how it should do it, and this helps eliminate waste by writing only the code required to make the tests pass.

Underscore find

The test specifications for our initial requirement are as follows:

describe("Given bicycleFinder", function() {
describe("when calling findBicycle()", function() {
   var bicycle;
   beforeEach(function() {
     bicycle = bicycleFinder.findBicycle("Urban Bike", 16);
   });
   it("then it should return an object", function() {
     expect(bicycle).toBeDefined();
   });
   it("then the 'type' property should be correct", function() {
     expect(bicycle.type).toEqual("Urban Bike");
   });
   it("then the 'rentPrice' property should be correct",
   function() {
     expect(bicycle.rentPrice).toEqual(15);
   });
});
});

The highlighted function call bicyleFinder.findBicycle() should return one bicycle object of the expected type and price as asserted by the tests.

Here is the implementation that satisfies the test specifications:

var bicycleFinder = (function() {
"use strict";
var getBicycles = function() {
   return [{
     id: 1,
     name: "A fast bike",
     type: "Road Bike",
     quantity: 10,
     rentPrice: 20,
     dateAdded: new Date(2015, 1, 2)
   }, {
   ...
   }, {
     id: 12,
     name: "A clown bike",
     type: "Children Bike",
     quantity: 2,
     rentPrice: 12,
     dateAdded: new Date(2014, 11, 1)
   }];
};
return {
   findBicycle: function(type, maxRentPrice) {
     var bicycles = getBicycles();
     return _.find(bicycles, function(bicycle) {
       return bicycle.type === type && bicycle.rentPrice <=
       maxRentPrice;
     });
   }
};
}());

The code returns the first bicycle that satisfies the search criteria ignoring the rest of the bicycles that might meet the same criteria.

You can browse the index.html file from the searching folder within the source code for this article to see the result of calling the bicyleFinder.findBicycle() function displayed on the browser via the propertyFormatter object.

Underscore some

There is a closely related function to _.find() with the signature _.some(list, [predicate], [context]). This function will return true if at least one item of the list collection satisfies the predicate function. The predicate parameter is optional, and if it is not specified, the _.some() function will return true if at least one item of the collection is not null. This makes the function a good candidate for implementing guard clauses. A guard clause is a function that ensures that a variable (usually a parameter) satisfies a specific condition before it is being used any further. The next example shows how _.some() is used to perform checks that are typical for a guard clause:

var list1 = [];
var list2 = [null, , undefined, {}];
var object1 = {};
var object2 = {
property1: null,
property3: true
};
if (!_.some(list1) && !_.some(object1)) {
alert("Collections list1 and object1 are not valid when calling _.some() over them.");
}
if(_.some(list2) && _.some(object2)){
alert("Collections list2 and object2 have at least one valid
item and they are valid when calling _.some() over them.");
}

If you execute this code in a browser, you will see both alerts being displayed. The first alert gets triggered when an empty array or an object without any properties defined are found. The second alert appears when we have an array with at least one element that is not null and is not undefined or when we have an object that has at least one property that evaluates as true.

Going back to our bicycle data, we will define a new requirement to showcase the use of _.some() in this context. We will implement a function that will ensure that we can find at least one bicycle of a specific type and with a maximum rent price. The code is very similar to the bicycleFinder.findBicycle() implementation with the difference that the new function returns true if the specific bicycle is found (rather than the actual object):

hasBicycle: function(type, maxRentPrice) {
var bicycles = getBicycles();
return _.some(bicycles, function(bicycle) {
   return bicycle.type === type && bicycle.rentPrice <=
   maxRentPrice;
});
}

You can find the tests specifications for this function in the spec/bicycleFinderSpec.js file from the searching example folder.

Underscore findWhere

Another function similar to _.find() has the signature _.findWhere(list, properties). This compares the property key-value pairs of each collection item from list with the property key-value pairs found on the properties object parameter. Usually, the properties parameter is an object literal that contains a subset of the properties of a collection item. The _.findWhere() function is useful when we need to extract a collection item matching an exact value compared to _.find() that can extract a collection item that matches a range of values or more complex criteria.

To showcase the function, we will implement a requirement that needs to search a bicycle that has a specific id value. This is how the test specifications look like:

describe("when calling findBicycleById()", function() {
var bicycle;
beforeEach(function() {
   bicycle = bicycleFinder.findBicycleById(6);
});
it("then it should return an object", function() {
   expect(bicycle).toBeDefined();
});
it("then the 'id' property should be correct", function() {
   expect(bicycle.id).toEqual(6);
});
});

And the next code snippet from the bicycleFinder.js file contains the actual implementation:

findBicycleById: function(id){
var bicycles = getBicycles();
return _.findWhere(bicycles, {id: id});
}

Underscore contains

In a similar vein, with the _.some() function, there is a _.contains(list, value) function that will return true if there is at least one item from the list collection that is equal to the value parameter. The equality check is based on the strict comparison operator === where the operands will be checked for both type and value equality.

We will implement a function that checks whether a bicycle with a specific id value exists in our collection:

hasBicycleWithId: function(id) {
var bicycles = getBicycles();
var bicycleIds = _.pluck(bicycles,"id");
return _.contains(bicycleIds, id);
}

Notice how the _.pluck(list, propertyName) function was used to create an array that stores the id property value for each collection item. In its implementation, _.pluck() is actually using _.map(), acting like a shortcut function for it.

Filtering

As we mentioned at the beginning of this section, Underscore provides powerful filtering functions, which are usually tasked with working on a subsection of a collection. We will reuse the same example data as before, and we will build some new functions to explore this functionality.

Underscore filter

We will start by defining a new requirement for our data where we need to build a function that retrieves all bicycles of a specific type and with a maximum rent price. This is how the test specifications looks like for the yet to be implemented function bicycleFinder.filterBicycles(type, maxRentPrice):

describe("when calling filterBicycles()", function() {
var bicycles;
beforeEach(function() {
   bicycles = bicycleFinder.filterBicycles("Urban Bike", 16);
});
it("then it should return two objects", function() {
   expect(bicycles).toBeDefined();
   expect(bicycles.length).toEqual(2);
});
it("then the 'type' property should be correct", function() {
   expect(bicycles[0].type).toEqual("Urban Bike");
   expect(bicycles[1].type).toEqual("Urban Bike");
});
it("then the 'rentPrice' property should be correct", function() {
   expect(bicycles[0].rentPrice).toEqual(15);
   expect(bicycles[1].rentPrice).toEqual(14);
});
});

The test expectations are assuming the function under test filterBicycles() returns an array, and they are asserting against each element of this array.

To implement the new function, we will use the _.filter(list, predicate, [context]) function that returns an array with all the items from the list collection that satisfy the predicate function. Here is our example implementation code:

filterBicycles: function(type, maxRentPrice) {
var bicycles = getBicycles();
return _.filter(bicycles, function(bicycle) {
   return bicycle.type === type && bicycle.rentPrice <=
   maxRentPrice;
});
}

The usage of the _.filter() function is very similar to the _.find() function with the only difference in the return type of these functions.

You can find this example together with the rest of examples from this subsection within the filtering folder from the source code for this article.

Underscore where

Underscore defines a shortcut function for _.filter() which is _.where(list, properties). This function is similar to the _.findWhere() function, and it uses the properties object parameter to compare and retrieve all the items from the list collection with matching properties.

To showcase the function, we defined a new requirement for our example data where we need to retrieve all bicycles of a specific type. This is the code that implements the requirement:

filterBicyclesByType: function(type) {
var bicycles = getBicycles();
return _.where(bicycles, {
   type: type
});
}

By using _.where(), we are in fact using a more compact and expressive version of _.filter() in scenarios where we need to perform exact value matches.

Underscore reject and partition

Underscore provides a useful function which is the opposite for _.filter() and has a similar signature: _.reject(list, predicate, [context]). Calling the function will return an array of values from the list collection that do not satisfy the predicate function. To show its usage we will implement a function that retrieves all bicycles with a rental price less than or equal with a given value. Here is the function implementation:

getAllBicyclesForSetRentPrice: function(setRentPrice) {
var bicycles = getBicycles();
return _.reject(bicycles, function(bicycle) {
   return bicycle.rentPrice > setRentPrice;
});
}

Using the _.filter() function alongside the _.reject() function with the same list collection and predicate function will allow us to partition the collection in two arrays. One array holds items that do satisfy the predicate function while the other holds items that do not satisfy the predicate function.

Underscore has a more convenient function that achieves the same result and this is _.partition(list, predicate). It returns an array that has two array elements: the first has the values that would be returned by calling _.filter() using the same input parameters and the second has the values for calling _.reject().

Underscore every

We mentioned _.some() as being a great function for implementing guard clauses. It is also worth mentioning another closely related function _.every(list, [predicate], [context]). The function will check every item of the list collection and will return true if every item satisfies the predicate function or if list is null, undefined or empty. If the predicate function is not specified the value of each item will be evaluated instead. If we use the same data from the guard clause example for _.some() we will get the opposite results as shown in the next example:

var list1 = [];
var list2 = [null, , undefined, {}];
var object1 = {};
var object2 = {
property1: null,
property3: true
};
if (_.every(list1) && _.every(object1)) {
alert("Collections list1 and object1 are valid when calling
_.every() over them.");
}
if(!_.every(list2) && !_.every(object2)){
alert("Collections list2 and object2 do not have all items valid
so they are not valid when calling _.every() over them.");
}

To ensure a collection is not null, undefined, or empty and each item is also not null or undefined we should use both _.some() and _.every() as part of the same check as shown in the next example:

var list1 = [{}];
var object1 = { property1: {}};
if (_.every(list1) && _.every(object1) && _.some(list1) &&
_.some(object1)) {
alert("Collections list1 and object1 are valid when calling both
_some() and _.every() over them.");
}

If the list1 object is an empty array or an empty object literal calling _.every() for it returns true while calling _some() returns false hence the need to use both functions when validating a collection.

These code examples demonstrate how you can build your own guard clauses or data validation rules by using simple Underscore functions.

Summary

In this article, we explored many of the collection specific functions provided by Underscore and demonstrated additional functionality. We continued with searching and filtering functions.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here