Combining Objects in JavaScript

children's blocks

The subject of inheritance in JavaScript has been needlessly complicated given that the underlying mechanism is elegant and simple. This article is appropriate for beginners as well as experienced JavaScript coders who would like a fresh look at how to combine objects. It assumes familiarity with basic JavaScript syntax.

Here we look at three tools provided by JavaScript:

  • Object.assign for cloning and mixins.
  • Object.create for prototype inheritance.
  • Proxy for more power than you can possibly imagine. BWAHAHAHAHA!

Immutable Objects

If we're going to use immutable objects in a functional programming paradigm (which I do most of the time) then Object.assign is our workhorse.

sheep = {  
  gender: 'female',
  color: 'white'
}

We can use it to clone an object.

> dolly = Object.assign({}, sheep)
{ gender: 'female', color: 'white' }
> dolly === sheep
false  

We can also use it merge two objects (like multiple inheritance).

mother = {  
  eyes: 'green',
  skin: 'light'
}

father = {  
  eyes: 'hazel',
  hair: 'black',
  skin: 'light'
}
> Object.assign({}, father, mother)
{ eyes: 'green', hair: 'black', skin: 'light' }

Notice the importance in the order of arguments to Object.assign: if we pass mother first then the child will get his father's eye color:

> Object.assign({}, mother, father)
{ eyes: 'hazel', skin: 'light', hair: 'black' }

If you want to be more particular about how properties are merged then you can write your own custom assign function.

Mutable Objects

Switching to the imperative paradigm, let's say you have an object representing a person that can change over time.

personal = {  
  name: 'Brendan Eich',
  telephone: '(415) 555 1212',
  email: [email protected]',
  twitter: [email protected]',

  getWebsite: function () {
    return 'http://www.' + this.email.split('@')[1]
  }
}

Now we want to relate to him professionally but instead of creating a whole new object with duplicate contact information, let's just clone him and change a couple of properties.

professional = Object.assign({}, personal)

Object.assign(professional, {  
  title: 'Founder, President and CEO',
  email: [email protected]'
})

This is cool: We can leverage properties from the personal object, including the method.

> professional.name
'Brendan Eich'  
> professional.email
[email protected]'  
> professional.getWebsite()
'http://www.Brave.com'  

But wait, if Brendan updates his contact information in the future or we improve the code in the method then the new object won't get the changes.

personal.telephone = '(650) 555 1212'

personal.getWebsite = function () {  
  return 'https://www.' + this.email.split('@')[1]
}
> professional.telephone
'(415) 555 1212'  
> professional.getWebsite()
'http://www.Brave.com'  

Because we made a clone of Brendan's state with Object.assign, the professional object doesn't update when his telephone number changes or we upgrade the method to use HTTPS.

In addition to creating a new object via cloning, JavaScript gives us Object.create to create a new object by dynamically inheriting properties from a prototype. A prototype is any ordinary object.

// 'professional' inherits from 'personal'.
professional = Object.create(personal)

// Override two properties.
Object.assign(professional, {  
  title: 'Founder, President and CEO',
  email: [email protected]'
})

You'll often see these two steps combined.

// Inherit and override properties in single step.
professional = Object.assign(Object.create(personal), {  
  title: 'Founder, President and CEO',
  email: [email protected]'
})

The difference is that instead of copying over the properties once, the current state of the original properties are checked whenever they are queried. Let's try making changes again to the prototype object and see what happens.

personal.telephone = '(650) 555 1212'

personal.getWebsite = function () {  
  return 'https://www.' + this.email.split('@')[1]
}
> professional.telephone
'(650) 555 1212'  
> professional.getWebsite()
'https://www.Brave.com'  

Proxy

JavaScript also provides a more powerful and general purpose method of defining object behavior called Proxy. I use it in my JSON-RPC library to make it appear as though server methods are available on a client. Let's demonstrate simple usage of Proxy by re-implementing Object.create.

// Reimplementation of Object.create using Proxy
function proxyCreate (prototype) {  
  const overrides = {}

  return new Proxy(prototype, {
    // Record changes to the overrides object without changing the prototype.
    set: function (target, property, value) {
      overrides[property] = value

      // Return 'true' to indicate success.
      return true
    },

    // Return an override if we have it, otherwise delegate to the prototype.
    get: function (target, property) {
      return property in overrides
        ? overrides[property]
        : prototype[property]
    }
  })
}

You can begin to see how powerful Proxy is with this rudimentary implementation of multiple inheritance.

function multipleInheritance (...prototypes) {  
  const overrides = {}

  return new Proxy(prototypes[0], {
    // Record changes to the overrides object without modifying the prototype.
    set: function (target, property, value) {
      overrides[property] = value

      // Return 'true' to indicate success.
      return true
    },

    // Return an override if we have it, otherwise delegate to the prototypes.
    get: function (target, property) {
      const prototype = [overrides]
        .concat(prototypes)
        .find((prototype) => property in prototype)

      if (prototype) {
        return prototype[property]
      }
    }
  })
}

Let's bring back our multiple inheritance example from before but this time see what happens when we mutate one of the parents.

mother = {  
  eyes: 'green',
  skin: 'light'
}

father = {  
  eyes: 'hazel',
  hair: 'black',
  skin: 'light'
}

child = multipleInheritance(mother, father)

> child.eyes
'green'  
> child.hair
'black'  
> father.hair = 'blonde'
'blonde'  
> child.hair
'blonde'  

Contraindications

JavaScript's tools of object combining are simple and elegant, are they not? Unfortunately you will see code in the wild that will make you squint. JavaScript has an unfortunate history of pretending to be something other than what it is in order to appeal to Java programmers, starting with changing its name from LiveScript to JavaScript. It has nothing to do with Java.

new operator

The new operator was added to the language because prototype inheritance was deemed too simple for Java programmers to understand and it was necessary to make the language appear more complicated than it actually is. When using new to create a new object you have to pretend there are classes in JavaScript (there aren't). Since there aren't classes, you invoke new on a function called a constructor that is intended to create an object from scratch:

function Foobar () {  
  this.localProperty = 42
}

foobar = new Foobar()  

Unfortunately, this leads to many problems:

  • You can't reuse an existing object—the constructor always creates a new one.
  • If the caller forgets to use the new operator when calling the constructor then it pollutes the global namespace with localProperty. You have to communicate to your users which functions are intended to be used with new and which aren't, a source of confusion.
  • You can't use object pools to increase performance.

See Constructors Are Bad For JavaScript and The Two Pillars of JavaScript for more details.

Object.create was added to the language specifically to address the shortcomings of new. Use it in factory functions instead of writing constructors. Don't use new unless you're forced to (such as when creating standard types, sigh).

class keyword

The class keyword was added to the language for the same reason as the new operator. Again, there aren't classes in JavaScript so this is just syntactic sugar that confuses and obscures the simple prototype system underneath. Friends don't let friends use class.

Conclusion

Sometimes JavaScript is like an awkward teenager trying to look like something she's not in order to impress her friends. If you can get past the surface inauthenticities you will find an elegant and powerful language inside.

View or Post Comments