This post is the first in a series of ES2015 posts. We'll be covering new JavaScript functionality every week for the coming two months.

ES2015 brings a lot of new functionality to the table. It might be a good idea to evaluate if your new or existing projects actually require a library such as lodash. We'll talk about several common usages of lodash functions that can be simply replaced by a native ES2015 implementation.

 

_.extend / _.merge

Let's start with _.extend and its related _.merge function. These functions are often used for combining multiple configuration properties in a single object.

const dst = { xeb: 0 };
const src1 = { foo: 1, bar: 2 };
const src2 = { foo: 3, baz: 4 };

_.extend(dst, src1, src2);

assert.deepEqual(dst, { xeb: 0, foo: 3, bar: 2, baz: 4 });

Using the new Object.assign method, the same behaviour is natively possible:

const dst2 = { xeb: 0 };

Object.assign(dst2, src1, src2);

assert.deepEqual(dst2, { xeb: 0, foo: 3, bar: 2, baz: 4 });

We're using Chai assertions to confirm the correct behaviour.

 

_.defaults / _.defaultsDeep

Sometimes when passing many parameters to a method, a config object is used. _.defaults and its related _.defaultsDeep function come in handy to define defaults in a certain structure for these config objects.

function someFuncExpectingConfig(config) {
  _.defaultsDeep(config, {
    text: 'default',
    colors: {
      bgColor: 'black',
      fgColor: 'white'
    }
  });
  return config;
}
let config = { colors: { bgColor: 'grey' } };

someFuncExpectingConfig(config);

assert.equal(config.text, 'default');
assert.equal(config.colors.bgColor, 'grey');
assert.equal(config.colors.fgColor, 'white');

With ES2015, you can now destructure these config objects into separate variables. Together with the new default param syntax we get:

function destructuringFuncExpectingConfig({
  text = 'default',
  colors: {
    bgColor: bgColor = 'black',
    fgColor: fgColor = 'white' }
  }) {
  return { text, bgColor, fgColor };
}

const config2 = destructuringFuncExpectingConfig({ colors: { bgColor: 'grey' } });

assert.equal(config2.text, 'default');
assert.equal(config2.bgColor, 'grey');
assert.equal(config2.fgColor, 'white');

 

_.find / _.findIndex

Searching in arrays using an predicate function is a clean way of separating behaviour and logic.

const arr = [{ name: 'A', id: 123 }, { name: 'B', id: 436 }, { name: 'C', id: 568 }];
function predicateB(val) {
 return val.name === 'B';
}

assert.equal(_.find(arr, predicateB).id, 436);
assert.equal(_.findIndex(arr, predicateB), 1);

In ES2015, this can be done in exactly the same way using Array.find.

assert.equal(Array.find(arr, predicateB).id, 436);
assert.equal(Array.findIndex(arr, predicateB), 1);

Note that we're not using the extended Array-syntax arr.find(predicate). This is not possible with Babel that was used to transpile this ES2015 code.

 

_.repeat, _.startsWith, _.endsWith and _.includes

Some very common but never natively supported string functions are _.repeat to repeat a string multiple times and _.startsWith / _.endsWith / _.includes to check if a string starts with, ends with or includes another string respectively.

assert.equal(_.repeat('ab', 3), 'ababab');
assert.isTrue(_.startsWith('ab', 'a'));
assert.isTrue(_.endsWith('ab', 'b'));
assert.isTrue(_.includes('abc', 'b'));

Strings now have a set of new builtin prototypical functions:

assert.equal('ab'.repeat(3), 'ababab');
assert.isTrue('ab'.startsWith('a'));
assert.isTrue('ab'.endsWith('b'));
assert.isTrue('abc'.includes('b'));

 

_.fill

A not-so-common function to fill an array with default values without looping explicitly is _.fill.

const filled = _.fill(new Array(3), 'a', 1);
assert.deepEqual(filled, [, 'a', 'a']);

It now has a drop-in replacement: Array.fill.

const filled2 = Array.fill(new Array(3), 'a', 1);
assert.deepEqual(filled2, [, 'a', 'a']);

 

_.isNaN, _.isFinite

Some type checks are quite tricky, and _.isNaN and _.isFinite fill in such gaps.

assert.isTrue(_.isNaN(NaN));
assert.isFalse(_.isFinite(Infinity));

Simply use the new Number builtins for these checks now:

assert.isTrue(Number.isNaN(NaN));
assert.isFalse(Number.isFinite(Infinity));

 

_.first, _.rest

Lodash comes with a set of functional programming-style functions, such as _.first (aliased as _.head) and _.rest (aliased _.tail) which get the first and rest of the values from an array respectively.

const elems = [1, 2, 3];

assert.equal(_.first(elems), 1);
assert.deepEqual(_.rest(elems), [2, 3]);

The syntactical power of the rest parameter together with destructuring replaces the need for these functions.

const [first, ...rest] = elems;

assert.equal(first, 1);
assert.deepEqual(rest, [2, 3]);

 

_.restParam

Specially written for ES5, lodash contains helper functions to mimic the behaviour of some ES2015 parts. An example is the _.restParam function that wraps a function and sends the last parameters as an array to the wrapped function:

function whatNames(what, names) {
 return what + ' ' + names.join(';');
}
const restWhatNames = _.restParam(whatNames);

assert.equal(restWhatNames('hi', 'a', 'b', 'c'), 'hi a;b;c');

Of course, in ES2015 you can simply use the rest parameter as intended.

function whatNamesWithRest(what, ...names) {
 return what + ' ' + names.join(';');
}

assert.equal(whatNamesWithRest('hi', 'a', 'b', 'c'), 'hi a;b;c');

 

_.spread

Another example is the _.spread function that wraps a function which takes an array and sends the array as separate parameters to the wrapped function:

function whoWhat(who, what) {
 return who + ' ' + what;
}
const spreadWhoWhat = _.spread(whoWhat);
const callArgs = ['yo', 'bro'];

assert.equal(spreadWhoWhat(callArgs), 'yo bro');

Again, in ES2015 you want to use the spread operator.

assert.equal(whoWhat(...callArgs), 'yo bro');

 

_.values, _.keys, _.pairs

A couple of functions exist to fetch all values, keys or value/key pairs of an object as an array:

const bar = { a: 1, b: 2, c: 3 };

const values = _.values(bar);
const keys = _.keys(bar);
const pairs = _.pairs(bar);

assert.deepEqual(values, [1, 2, 3]);
assert.deepEqual(keys, ['a', 'b', 'c']);
assert.deepEqual(pairs, [['a', 1], ['b', 2], ['c', 3]]);

Now you can use the Object builtins:

const values2 = Object.values(bar);
const keys2 = Object.keys(bar);
const pairs2 = Object.entries(bar);

assert.deepEqual(values2, [1, 2, 3]);
assert.deepEqual(keys2, ['a', 'b', 'c']);
assert.deepEqual(pairs2, [['a', 1], ['b', 2], ['c', 3]]);

 

_.forEach (for looping over object properties)

Looping over the properties of an object is often done using a helper function, as there are some caveats such as skipping unrelated properties. _.forEach can be used for this.

const foo = { a: 1, b: 2, c: 3 };
let sum = 0;
let lastKey = undefined;

_.forEach(foo, function (value, key) {
  sum += value;
  lastKey = key;
});

assert.equal(sum, 6);
assert.equal(lastKey, 'c');

With ES2015 there's a clean way of looping over Object.entries and destructuring them:

sum = 0;
lastKey = undefined;
for (let [key, value] of Object.entries(foo)) {
  sum += value;
  lastKey = key;
}

assert.equal(sum, 6);
assert.equal(lastKey, 'c');

 

_.get

Often when having nested structures, a path selector can help in selecting the right variable. _.get is created for such an occasion.

const obj = { a: [{}, { b: { c: 3 } }] };

const getC = _.get(obj, 'a[1].b.c');

assert.equal(getC, 3);

Although ES2015 does not has a native equivalent for path selectors, you can use destructuring as a way of 'selecting' a specific value.

let a, b, c;
({ a : [, { b: { c } }]} = obj);

assert.equal(c, 3);

 

_.range

A very Python-esque function that creates an array of integer values, with an optional step size.

const range = _.range(5, 10, 2);
assert.deepEqual(range, [5, 7, 9]);

As a nice ES2015 alternative, you can use a generator function and the spread operator to replace it:

function* rangeGen(from, to, step = 1) {
  for (let i = from; i < to; i += step) {
    yield i;
  }
}

const range2 = [...rangeGen(5, 10, 2)];

assert.deepEqual(range2, [5, 7, 9]);

A nice side-effect of a generator function is it's laziness. It is possible to use the range generator without generating the entire array first, which can come in handy when memory usage should be minimal.

Conclusion

Just like kicking the jQuery habit, we've seen that there are alternatives to some lodash functions, and it can be preferable to use as little of these functions as possible. Keep in mind that the lodash library offers a consistent API that developers are probably familiar with. Only swap it out if the ES2015 benefits outweigh the consistency gains (for instance, when performance is an issue).

For reference, you can find the above code snippets at this repo. You can run them yourself using webpack and Babel.