2. Common concepts

Before diving into details of lodash functions, we start from some common concepts in lodash.

2.1 Truthy and falsy

Truthy and falsy values are very important when using lodash predicates. false, 0, ""(empty string), null, undefined and NaN are falsy values in JavaScript. All other values are truthy values.

2.2 SameValueZero

SameValueZero is the algorithm of how to compare two values in lodash. It’s similar to JavaScript “strict equality comparison” (===), except the handling of NaN. It always makes developers confused as NaN === NaN returns false. SameValueZero removes that confusion, so NaN is the same to NaN in SameValueZero algorithm.

2.3 Predicates

Predicate functions only return truthy or falsy values. They are used frequently in lodash. For example, when filtering a collection, a predicate function is required to determine what kind of elements should be kept.

Predicate functions can be written as plain old JavaScript functions. Lodash also provides some helper functions to generate predicate functions for common use cases.

2.3.1 matches

_.matches(source) takes a source object and creates a new function which performs a deep comparison between the given object and the source object. _.matches supports comparison of different types of data, including booleans, numbers, strings, Date objects, RegExp objects, Object objects and arrays. Listing 2.1 shows how _.matches works by comparing strings and objects.

Listing 2.1 Match by object comparison
const matches = require('lodash/matches');

describe('matches', () => {
  it('should match strings', () => {
    let f = matches('hello');
    expect(f('world')).toBe(false);
    expect(f('hello')).toBe(true);
  });

  it('should match objects', () => {
    let f = matches([{a: 1}, {b: 2}]);
    expect(f([{a: 1}, {b: 3}])).toBe(false);
  });
});

2.3.2 matchesProperty

_.matchesProperty(path, value) takes a property path and the expected value of this property path to create a new function that checks if the given object’s value of the same property path matches the expected value. Listing 2.2 shows how _.matchesProperty works by matching simple property name, built-in property and nested property path.

Listing 2.2 Match by comparing property value
const matchesProperty = require('lodash/matchesProperty');

describe('matchesProperty', () => {
  it('should match property name', () => {
    let f = matchesProperty('name', 'Alex');
    expect(f({name: 'Alex'})).toBe(true);
  });

  it('should match built-in property', () => {
    let f = matchesProperty('length', 5);
    expect(f('hello')).toBe(true);
  });

  it('should match nested path', () => {
    let f = matchesProperty('user.name', 'Alex');
    expect(f({user: {name: 'Alex'}})).toBe(true);
  });
});

2.3.3 property

_.property(path) takes a property path and creates a new function which returns the value of this property path in the given object. _.property can be used to create predicate functions with property values converted to truthy or falsy values.

Listing 2.3 Extract property value
const property = require('lodash/property');

describe('property', () => {
  it('should extract property value', () => {
    let f = property('name');
    expect(f({name: 'Alex'})).toBe('Alex');
  });
});

For lodash functions which accept predicates, e.g. _.find and _.filter, predicates can be specified using functions, strings, and objects.

  • If a function is provided, it’s used directly. The predicate matches if the function returns a truthy value.
  • If only a string is provided, it’s used to create a function using _.property as the predicate.
  • If an array that contains a string and a value is provided, the string and the value are used to create a function using _matchesProperty as the predicate.
  • If an object is provided, it’s used to create a function using _.matches as the predicate.

For example, given an array shown in Listing 2.4,

Listing 2.4 Example input JSON array
[
  {
    "name": "Alex",
    "age": 30,
    "is_premium": false
  },
  {
    "name": "Bob",
    "age": 20,
    "is_premium": true
  },
  {
    "name": "Mary",
    "age": 25,
    "is_premium": false
  }
]

_.find returns the first matching element in the array. A JavaScript function can be used as the predicate to _.find. In Listing 2.5, we find the first element with age greater than 18 in the array users. The result is the first element with the name Alex.

Listing 2.5 Find using a function
const find = require('lodash/find');

describe('find with different predicates', () => {
  it('should find with a function', () => {
    let user = find(users, user => user.age > 18);
    expect(user).toBeDefined();
    expect(user.name).toBe('Alex');
  });
});

If a string is passed as the predicate, it’s treated as a property name of objects in the array. In Listing 2.6, we find the first element with truthy value of the property is_premium in the array. The actual used predicate is _.property('is_premium'). The result is the second element with the name Bob.

Listing 2.6 Find using a property value
it('should find with a property value', () => {
  let user = find(users, 'is_premium');
  expect(user).toBeDefined();
  expect(user.name).toBe('Bob');
});

If an object is passed as the predicate, it’s treated as a search example. Objects in returned results must have exactly the same values for all the corresponding properties provided in the search example. In Listing 2.7, we find the first element with the value of the property name equals to Alex in the array. The actual used predicate is _.matches({ name: 'Alex' }).

Listing 2.7 Find using an object
it('should find with an object', () => {
  let user = find(users, { name: 'Alex' });
  expect(user).toBeDefined();
  expect(user.name).toBe('Alex');
});

2.4 Iteratees

Iteratees are used by lodash functions which require iterating through a collection. Iteratee is invoked for each element in the collection and the result is used instead of the original element. Iteratees are typically used to transform collections. A typical usage of iteratee is in the function _.map. The second argument of _.map is the iteratee. The result of applying iteratee to each element in the collection is collected and returned. In Listing 2.8, we use a function to transform input array [1, 2, 3] to [3, 6, 9].

Listing 2.8 map using an iteratee function
const map = require('lodash/map');

describe('map with iteratees', () => {
  it('should map with an iteratee function', () => {
    let result = map([1, 2, 3], n => n * 3);
    expect(result).toEqual([3, 6, 9]);
  });
});

2.4.1 Iteratee shorthand

When iteratee functions are required, we can also use the similar syntax as predicate functions to quickly create them. These iteratee shorthands use methods _.matches, _.matchesProperty or _.property behind the scene.

In the second invocation of _.map in Listing 2.9, the second argument of _.map must be an array to indicate that it uses _.matchesProperty.

Listing 2.9 map using an iteratee shorthand
it('should map with iteratee shorthands', () => {
  let result = map(users, {name: 'Alex'});
  expect(result).toEqual([true, false, false]);

  result = map(users, ['name', 'Alex']);
  expect(result).toEqual([true, false, false]);

  result = map(users, 'name');
  expect(result).toEqual(['Alex', 'Bob', 'Mary']);
});

2.5 this binding

In Lodash 3, we can use the argument thisArg to specify the value of this binding. In Lodash 4, thisArg has been removed in most methods. To specify the binding object, _.bind should be used explicitly. In Listing 2.10, when the function add is invoked, this value is bound to obj.

Listing 2.10 map with this binding using _.bind
const map = require('lodash/map');
const bind = require('lodash/bind');

describe('this binding', () => {
  it('should bind to this', () => {
    const obj = {
      val: 10,
      add: function(n) {
        return this.val + n;
      }
    };
    let result = map([1, 2, 3], bind(obj.add, obj));
    expect(result).toEqual([11, 12, 13]);
  });
});