How To Copy Objects in JavaScript

There are many ways to copy objects in JS but most of the time such operation doesn’t do what we expect.

Are properties copied by reference? Are sub properties merged or replaced? Are accessors, like getters and setters, preserved? And what about Symbols or non enumerable properties?

Shallow VS Deep Copy

Whenever we do an operation like a.prop = b.prop we are performing a shallow copy, meaning that if b.prop is an object, modifying it through a.prop will reflect changes in b.prop too. We copied a reference and not its value.

var b = {shallow: {test: 'OK'}};

var a = {};
a.shallow = b.shallow;

// if we modify it
a.shallow.test = 'NOPE';

// it will be reflected through b
b.shallow.test; // "NOPE"

In order to obtain a deep copy of b.shallow we need to perform a similar operation:

var b = {shallow: {test: 'OK'}};

// procedural deep copy example
var a = {};
a.shallow = {};
a.shallow.test = 'NOPE';

// not reflected through b
b.shallow.test; // "OK"

Creating a new object per each property that is not a primitive value, is also more expensive than a shallow copy.

However, if we care about data consistency and ownership, once we have a copy of another object we don’t want to reflect our changes in there.

But what are the options in current JS panorama?

Object.assign(target, source1[, sourceN])

Directly from ES6/2015 specs, Object.assign performs shallow copy and it merges own enumerable keys, giving priority right to left.

var a = {};
var b = {a: {b: true}, 'b': 2};
var c = {a: {c: true}, 'c': 3};

// shallow merge
Object.assign(a, b, c);

a.b; // 2
a.c; // 3

// which `a` property was copied?
// the c one, right to left priority
a.a === c.a;

// what if we modify a.a ?
a.a.test = 'will it blend?';

c.a.test; // "will it belnd"

Object.assign Side Effects

There are at least two main subtle problems most developers are not aware of, when properties are copied over using O.assign

var uid = Symbol('uid');

var special = {
  get next() {
    return ++this.counter;
  },
  counter: 0
};

special[uid] = Math.random();


// what could possibly go wrong?
var notSpecial = Object.assign({}, special);

notSpecial.counter; // 1 if `next` was copied before `counter`
                    // 0 if keys order is different: WATCH OUT!

notSpecial.next;    // 1 in any case

notSpecial[uid];    // same as special[uid]

As we can see it’s a terrible idea to copy properties assuming these were defined as data instead of accessors, it’s possibly the only case where the key definition order matters at a point that the entire program could fail or change.

// special with properties in a different order
var special = {
  counter: 0,
  get next() {
    return ++this.counter;
  }
};

var notSpecial = Object.assign({}, special);

notSpecial.counter; // bets accepted 'cause it's engine dependent!
                    // it's probably 0 this time, not 1

notSpecial.next;    // 1 in any case

As summary, with Object.assign we lose accessors and, worst part of it, we invoke the eventual getter in order to assign the resulting data, the order in which properties are defined might compromise results in different engines, plus everything is shallow.

This is some sort of disaster, if you ask me, and the reason is that the proposal, or the common case, was not considering ES5 descriptors, it was based on the old ES3 world full of for/in loops and hasOwnProperty checks, a world where accessors were there already as de-facto non standard but rarely used since IE8 wasn’t compatible.

If you want my advice: don’t use Object.assign for anything but simple shallow copies or merges with data you know for sure does NOT contain accessors. Avoid it in every library which aim is to be publicly available, it’s an unpredictable error prone approach.

A patched version of Object.assign

Keeping it shallow, ignoring Symbols, and copying accessors, we can easily create our own “free from surprisesObject.assign equivalent.

function assign(target) {
  for (var
    hOP = Object.prototype.hasOwnProperty,
    copy = function (key) {
      if (!hOP.call(target, key)) {
        Object.defineProperty(
          target,
          key,
          Object.getOwnPropertyDescriptor(this, key)
        );
      }
    },
    i = arguments.length;
    1 < i--;
    Object.keys(arguments[i]).forEach(copy, arguments[i])
  ){}
  return target;
}

At this point not only accessors will be preserved, these won’t be invoked during such operation.

var special = {
  get next() {
    return ++this.counter;
  },
  counter: 0
};

var reSpecial = assign({}, special);
reSpecial.counter;  // 0
reSpecial.next;     // 1
reSpecial.next;     // 2
reSpecial.next;     // 3

// did it affect special?
special.counter;    // 0 ... nope
special.next;       // 1
special.next;       // 2 ... and so on

Copying all own keys

The latest specification has a Reflect.ownKeys method which is the equivalent of the following operation:

function ownKeys(object) {
  return Object.getOwnPropertyNames(object).concat(
    Object.getOwnPropertySymbols(object)
  );
}

Above functionality gives us the ability to retrieve every own descriptor too, and simulate the Object.getOwnPropertyDescriptors proposal for the next ECMAScript specification.

function getOwnPropertyDescriptors(object) {
  return ownKeys(object).reduce(function (d, key) {
    d[key] = Object.getOwnPropertyDescriptor(object, key);
    return d;
  }, {});
}

At this point we could simply perform two objects shallow copy through this basic, semantic, procedure:

// full properties shallow copy
Object.defineProperties(
  target,
  getOwnPropertyDescriptors(source)
);

Deep Copy

There is no native mechanism to perform a deep copy in JavaScript and one of the reason is that it’s quite complicated. For instance, a function in JavaScript is still an object but it cannot be actually copied in any way which might be a side effect.

There could be circular references too that are usually hard to deal with in a merge plus it could be a quite slow operation.

In any case, I’ve created an utility that does it: it’s called cloner and it’s already in npm.

Its algorithm has been already tested via es-class and dom-class and it seems to work pretty well.

There are basically two submodules included: shallow and deep. Each of them has two methods: copy and merge.

// insteada of Object.assign(a, b)
var a = cloner.shallow.copy(b);

// instead of merging recursively
var a = {a: 1, b: 2};
var b = cloner.shallow.merge({a: 3}, a);
b; // {a: 3, b: 2}


// instead of manually deep copying
var a = {test: {a: 1}};
var b = cloner.deep.copy(a);
b.test.a = 2;
a.test.a; // 1

// same is for deep merge
var b = cloner.deep.merge({a: 3}, a);

Right now every single key is copied or cloned over the target, not only own properties, and not only enumerable, this makes the utility handy to merge native classes prototypes too or augment mixins.

At least now we have all details we need to know, before copying a property from an object to another.

Andrea Giammarchi

Fullstack Web Developer, Senior Software Engineer, Team Leader, Architect, Node.js, JavaScript, HTML5, IoT, Trainer, Publisher, Technical Editor