JS · underscore.jsMar 12, 2014 · 3 min read

Unpacking Underscore: Clone and Extend

Underscore's _.clone and _.extend are both shallow — and the difference between a shallow and deep copy is exactly where bugs hide. A look at what each method actually does under the hood, plus a deepExtend pattern.

In my post comparing slice versus splice, I mentioned that slice creates a shallow copy of an array. So what exactly is a shallow copy, and what makes a copy "deep"?

Extend

I'll start by taking a look at the _.extend():

JavaScript
_.extend = function(destination) {
  each(slice.call(arguments, 1), function(source) {
    if (source) {
      for (var prop in source) {
        destination[prop] = source[prop];
      }
    }
  });
  return destination;
};

You call it with _.extend(destination, source), and you can have more than one source. It iterates over the source(s), and for each source, copies the properties to the destination object. This will override any properties set on the destination, as well as properties set by previous sources, and the resulting object is returned.

It is worth noting: if a property is a reference type - that is, it is a pointer to an object, the pointer would be copied over and not the entire object.

Clone

Since objects are reference types, it is often useful to have a copy that you can modify without changing the original. For that purpose, underscore provides a _.clone() method.

JavaScript
_.clone = function(obj) {
  if (!_.isObject(obj)) return obj;
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

If the argument obj is a primitive type, then we simply return it.

For arrays, we can create a clone by calling obj.slice() &emdash; as previously discussed, slice allows you to create an array and leave the original unchanged.

To create a shallow clone of object, we can call _.extend(). Extending one object with another is similar to "merging" the two objects. If you merge an object with an empty object, what you'll get in return is a copy of the original object.

Deep Extend

Although underscore doesn't provide a deepExtend method, I came across this snippet from http://youmightnotneedjquery.com/#deep_extend:
JavaScript
var deepExtend = function(out) {
  out = out || {};

for (var i = 1; i < arguments.length; i++) {
    var obj = arguments[i];

if (!obj)
      continue;

for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (typeof obj[key] === 'object')
          deepExtend(out[key], obj[key]);
        else
          out[key] = obj[key];
      }
    }
  }

return out;
};

deepExtend({}, objA);

This is similar to calling $.extend(true, {}, objA); in jQuery. A few things worth noting:

  • Check hasOwnProperty so that we aren't copying stuff from the prototype
  • Check if the property is a reference type. If so, recursively call deepExtend to dive into the object and copy its properties over.