Composition using Prototypal Inheritance

Jose Antonio Blanco
Javascript Etudes
Published in
9 min readApr 11, 2016

…or how to use prototypal inheritance for nefarious purposes…

Introitus

Composition is a widely used technique in other programming languages that provide native syntax for it. In Javascript, composition is not immediately evident. However, the flexibility of the Javascript objects, along with its prototypal inheritance, provide interesting prospects about how to achieve it.

My goals for this study are:

  • Have a clear understanding on the differences between inheritance and composition.
  • Explore the possibility of combining both techniques to achieve a different way to composing objects.
  • Using factory functions to create complex object hierarchies as transparently as possible.

Andante: Inheritance vs Composition

Inheritance is a well-known technique that is integral part of many of the programming languages used today. Most of the object-oriented language use the concept of class, as a way to describe the common behavior of a set of objects, and along with class you also get the concept of inheritance, so you can describe and group the common behaviors of a set of classes into a single superclass.

A classical example about object-oriented inheritance is this:

The inheritance describes a “is-a” relationship between the classes, so any object created or instantiated from the Dog class, will not get only all the behaviors from its class, but through inheritance, will automatically get the behaviors described in the Mammal and Animal classes.

Composition tries to achieve something similar in a different way. The basic concept is to combine several objects that have distinct traits into a single object that is bigger than the sum of its parts.

In other programming languages, like Swift or C#, this extensibility is achieved using mechanisms like protocols or interfaces. Whatever name is used, the main idea is to define a group of traits that define certain behavior of a given object.

The formal way to describe composition is using a “has-a” relationship. This way you can describe what the objects does, instead of what the object is. Let’s look at a composition example:

We can describe a house as a composition of several objects (rooms in this case), and the resulting object House provides the necessary wiring and piping to make the objects work together as a whole.

Even more, reusing the same room objects, we can build different kinds of objects (a house, a hotel, a skyscraper…), just changing how we compose these basic objects. Like LEGO blocks, you still have to connect the objects in a certain way, but you can build virtually unlimited types of constructions using the same set of blocks.

Ossia: Composing objects

There are several ways to achieve object composition in Javascript. One of the simplest is just copying property values from the objects you are composing into a target object, using hasOwnProperty() to ensure that the property belongs to the object and is not coming from the prototype chain:

> var trait1 = { a: 100 };
> var trait2 = { b: 200 };
> var x = compose(trait1, trait2);> x.a
100
> x.b
200

If you are using Javascript ES6, there’s even a simpler way, just using Object.assign():

> var trait1 = { a: 100 };
> var trait2 = { b: 200 };
> var x = Object.assign({}, trait1, trait2);> x.a
100
> x.b
200

Largo: First Attempt

My first attempt to achieve composition using prototypal inheritance was this:

The basic idea is to iterate through all the objects passed to the compose() function as arguments, and for each object, create a “clone” using Object.assign() and use this clone as the prototype for a new object using Object.create(). This new object would be used as the prototype in the next iteration.

This seemed to work, at least initially. To analyze what was happening, I created this helper function:

This function returns the whole prototype chain as an array, from top to bottom, so we can easily visualize how compose() has built the chain (probably I shouldn’t be using __proto__ property, you can change that to Object.getPrototypeOf() if you feel better using the standard).

So, let’s try a simple test:

> var trait = { a : 100 };> var x = compose(trait);
> x.a
100
> protoStack(trait)
[ { a: 100 }, {} ]
> protoStack(x)
[ {}, { a: 100 }, {} ]

We can see that the prototype of x is a shallow clone of the original trait object (that last empty object is Object.prototype and usually it’s the end of the prototype chain)

But if we do a nested composition:

> var trait1 = { a: 100 };
> var trait2 = { b: 200 };
> var x = compose(trait1);
> var y = compose(trait2);
> var z = compose(x, y);
> z.a
undefined
> z.b
undefined
> protoStack(z)
[ {}, {} ]

We realize that we are overwriting and losing the prototype chains of x and y. So this might work for simpler scenarios, but not a as a general one-fits-all solution.

Crescendo: Cloning the prototype chain

So it became evident that, for this compose method to work, it has to clone not only the original objects, but also their entire prototype chains.

So we would need something like this:

x’ and y’ are clones of x and y respectively

We already have a function that transforms the prototype chain of an object into an array, something that we can transform and iterate:

This version uses a slightly modified version of protoStack() as a helper, and given a set of objects as arguments, iterates over the prototype chain of each object and builds a new prototype chain, joining this set of smaller chains into a bigger one, like a necklace.

> var trait1 = { a: 100 };
> var trait2 = { b: 200 };
> var x = compose(trait1);
> var y = compose(trait2);
> var z = compose(x, y);
> z.a
100
> z.b
200
> protoStack(x)
[ {}, { a: 100 }, {} ]
> protoStack(y)
[ {}, { b: 200 }, {} ]
> protoStack(z)
[ {}, {}, { b: 200 }, {}, { a: 100 }, {} ]

Notice that, after chaining all the prototypes in a new object, we create a new object with this composition as the prototype, with that last call to Object.create().

Also, we can check that, if the composed objects share a property with the same name, the last object composed wins:

> var trait1 = { a: 100 };
> var trait2 = { b: 200 };
> var x = compose(trait1);
> x.id = "XXX";
> var y = compose(trait2);
> y.id = "YYY";
> var z = compose(x, y);
> z.id
"YYY"
> protoStack(z)
[ {}, { id: 'YYY' }, { b: 200 }, { id: 'XXX' }, { a: 100 }, {} ]

It’s interesting to remark that, in the generated prototype chain for z, both values for id still exist, but Javascript will return the first found when searching in the prototype chain.

Scherzo: Simplifiying

In this iteration my goal was to remove the need to create an array with the prototype chain for each argument. So the answer was, why not, recursion:

The makeProto() function will recursively go down to the bottom of the prototype chain first, and then as it returns, it will build a new prototype chain using Object.create() and Object.assign().

The base argument allows to define which object will be “bottom” of this new chain. When invoked from the main body, this argument will allow us to chain each cloned prototype chain as we did in the previous version.

For this version the code looks cleaner, and conceptually it’s a better approach, although the complexity is the same.

Allegro ma non troppo: Assigning is not copying

One of the less known side effects of Object.assign() is that, as the name precisely implies, assigns the current value from a property to another object. This is usually the desired effect, except when you have properties with getters and setters:

> var x = {
a:5,
get b() { return this.a+1 }
}
> x
{ a: 5, b: [Getter] }
> y = Object.assign({},x);
{ a: 5, b: 6 }

In this case, the current value obtained from the getter is assigned to the target object, but all the logic behind that getter and/or setter is lost.

Interestingly, Object.create() admits a second parameter that allows to add new properties to the newly created object, using an object that contains property descriptors. These property descriptors, in addition to the property value, will also indicate if the property is enumerable, writable or configurable, along with the getter or setter if defined.

We can get the property descriptor for a single property using Object.getOwnPropertyDescriptor(), but there is no function for getting the descriptors of all the object’s own properties. We can build that function using Object.getOwnPropertyNames() to iterate through all the properties:

Using this function, we could “clone” an object that has getters or setters with Object.create():

> var x = {
a:5,
get b() { return this.a+1 }
}
> x
{ a: 5, b: [Getter] }
> y = Object.create({},getPropertiesDescriptor(x));
{ a: 5, b: [Getter] }

As a bonus, getPropertiesDescriptor() can be used with Javascript ES5, while Object.assign() requires ES6.

Allegro Assai: Composing in ES5

With this new tool in our belt, we can create a new version of compose() that can be used in ES5, and in addition, clones properties exactly as they were defined in the original objects:

We are still using a similar recursive technique in makeProto() to reach the bottom of the prototype chain, and then build up the new chain using Object.create().

Let’s try this new version, adding a getter in one of the traits to check that works as expected:

> var trait1 = { a: 100, get k() { return 0; } };
> var trait2 = { b: 200 };
> var x = compose(trait1);
> x.id = "XXX";
> var y = compose(trait2);
> y.id = "YYY";
> var z = compose(x, y);
> z.id
"YYY"
> protoStack(z)
[ {}, { id: 'YYY' }, { b: 200 }, { id: 'XXX' }, { a: 100, k: [Getter] }, {} ]

Let’s see this result in a graph:

Finale: Compressing the chain

This last graph highlights the main weakness of this approach: each nested composition will enlarge more and more the prototype chain, and this is not a good thing. However, we can achieve a similar effect if we compose all the prototype chain in a single “properties descriptor”, and then use this descriptor to create the composition:

Notice that this time we are not returning a descriptor in getProtoProperties(), but using the descriptor object defined within the closure as “accumulator” of all the properties. We have also combined in this function the prototype chain traversal with that recursive call.

Testing the results:

> var trait1 = { a: 100, get k() { return 0; } };
> var trait2 = { b: 200 };
> var x = compose(trait1);
> x.id = "XXX";
> var y = compose(trait2);
> y.id = "YYY";
> var z = compose(x, y);
> z.id
"YYY"
> protoStack(z)
[ {}, { a: 100, k: [Getter], id: 'YYY', b: 200 }, {} ]

or in a more graphical way:

This version of compose() ensures that any composed object will have only three levels in the prototype chain (the newly created object, the composition object, and Object.prototype), regardless of the number or complexity of the composed objects.

Coda

There are several things that I like in this approach to composition:

  • It’s Simple: Regardless of the complexity of the compose() function, once the object is composed, that’s it. It’s just a plain Javascript object that uses the default prototypal inheritance to its advantage.
  • It’s Clean: The composed object “hides” all the composed traits behind the prototype chain, i.e. you can use Object.getOwnPropertyNames() or hasOwnProperty() to get only the object own properties, not all the properties from the composition.
  • It’s Complete: Using property descriptors provides a more comprehensive way to clone object properties

--

--