Prototypal Inheritance With Stamps

JavaScript 6 6 Comments

Stampit is one year old as of February 10, 2014. I wrote it to demonstrate prototypal inheritance with factories for the book, “Programming JavaScript Applications”. To celebrate its birthday (a little late), here is a free excerpt from the book:

JavaScript’s object capabilities are really flexible, but Object.create() isn’t the easiest way to create a fully featured object. There are still quite a few hoops to jump through just to create a collection of flyweight objects that support data privacy. It gets even more complicated if you want to combine features from more than one source object.

Lots of libraries provide mechanisms to mimic classical inheritance, but there are few widespread libraries that simplify prototypal object creation, and certainly none that stand out as the gold standard. There is sugar for faking classes coming to JavaScript (I strongly discourage using it). What would it look like if we created sugar for prototypal OO that supported all of the best features JavaScript has to offer?

When thinking about object creation in JavaScript, it helps to ask, what are the important features of objects in JavaScript?

  • Delegate prototype

  • Instance state

  • Encapsulation

A stamp is a factory function which has public properties that specify a delegate prototype, default instance state, and a function that sets up encapsulation for object instances. Stamps utilize three different types of inheritance to create the new object:

  • Delegate prototype – Delegation / Differential Inheritance

  • Instance state – Cloning / Concatenative Inheritance / Mixins

  • Encapsulation – Functional Inheritance

Stampit is a library written to demonstrate how we might use sugar to simplify prototypal OO. It exports a single function. Here’s the signature:

Here’s a more detailed look at how you’d use it to create an object from scratch:

Notice that the .create() method was called on the returned stamp in order to return the testObj instance. The .create() method simply returns a new object instance from the stamp. As you can see from the tests, all of the great features that make JavaScript’s object system special are available without jumping through all the hoops of setting up your own factory function, figuring out where to store prototypes, etc…

The stamps returned by stampit() also contain methods which can be chained to further define the stamp. This is equivalent to the stamp above:

The new object gets created with Object.create() using methods as the delegate prototype. Delegate methods are shared among all object instances, which conserves memory resources. If you change a prototype property at runtime, the value change is reflected on every instance of the object. To demonstrate:

The .state() method uses concatenative inheritance, which creates a copy of each property from the state prototypes to the new object, allowing you to safely store instance state. All stamps take an options hash which will be mixed into the instance properties, so it’s trivial to initialize a new object:

The .enclose() method uses functional inheritance, which invokes the functions you pass in against the newly created object. You can pass in any number of .enclose() functions. Private data will not collide, because a unique closure will be created for each function. Privileged methods will override methods of the same name. Last in wins.

Sometimes it’s useful to initialize an object with parameters that don’t correspond 1:1 with object properties. The way I prefer to do that is to decouple object instantiation and object initialization. You can do that by creating setters on the object:

Of course, creating new objects is just scratching the surface of JavaScript’s OO capabilities. What you’re about to see is not possible with any currently existing popular JavaScript class library. It’s not possible with the ES6 class keyword, either.

First, you’ll use a closure to create data privacy:

It uses function scope to encapsulate private data. Note that the getter must be defined inside the function in order to access the closure variables. All privileged functions in JavaScript must be defined within the same scope as the private variables they need access to.

And another:

Those a‘s are not a typo. The point is to demonstrate that a and b‘s private variables won’t clash. Here’s where it gets interesting:

Stampit’s .compose() method allows you to inherit all three types of prototypes from any number of stamps. The above example demonstrates inheritance from multiple ancestors, including private data. Classical OO as we know it today has got nothing on this.

Each stamp has a special property called fixed, which stores methods, state, and enclose prototypes. When the stamp is invoked, the state prototype is copied so that it’s instance safe, the methods property is used as the delegate prototype, and each enclose function is called in the order that they were created to set up data privacy (last in wins in the case of name collisions).

The .compose() method works a bit like $.extend(), but instead of extending objects with the properties of other objects, it extends new objects with the fixed prototypes from the factories you pass in, and then calls stampit and passes them into a new stamp. Like $.extend(), _.extend() and so on, last in wins when names collide, which makes it trivial to override stamp features.

Conclusion

Stampit is in production use in web applications with tens of millions of users, so the full source contains several shims for older browsers that enable JavaScript ES5 features. Despite that, it weighs in at about 4k minified and gzipped. The meat of it is about 90 lines of code (without the shims and comments).

As you can see, JavaScript’s object system is flexible and powerful enough to do some really amazing things with very little effort. Imagine how much more fun it could be with a little more built-in language sugar for working with prototypes. Object.create() is a good start, but we could do a lot more. By now you should have a whole new respect for prototypes, and JavaScript’s object system.

Stampit has been in production use since Feb, 2013. The API has been remarkably stable, and the code itself has been quite reliable. It has thorough unit test suite and good documentation. There is one method that I recommend with caution: .convertConstructor() is not meant to be used with constructors that require parameters. I wrote the method to make it easier to inherit from traditional constructors such as Node’s EventEmitter(), and it works well for similarly limited use-cases.

Other than that, Stampit should be considered stable and production ready. What are you building with Stampit? I’d love to hear about it in the comments.

Next steps:


    • Eric Elliott - February 26, 2014

      Easy enough for the kids to play along!  If you can get them to sit through this talk: http://www.slideshare.net/okjungsoo/stampit-24251685

      • Fascinating stuff. Looks like it can really help make your code more robust.

        So does your compose method essentially implement Multiple Inheritance? http://en.wikipedia.org/wiki/Multiple_inheritance

        Also, your blog software failed to email me that you had replied.

        • Eric Elliott - February 26, 2014

          It delivers equivalent results, but uses different mechanisms than classical multiple inheritance. Instead of inheriting from multiple classes like you would in languages with direct support like C++, it combines prototype mixins (closer to Scala’s mixin class implementation of multiple inheritance) and function composition, which is applied to the .enclose() prototypes.

          The prototype mixin approach eliminates the uncertainty of the diamond problem in classical multiple inheritance. You always know which version of a method you’ll get based on its order of precedence (last in wins).

          You may be familiar with multiple inheritance in C#, which may make use of protocols or interfaces to simulate multiple inheritance (C#, Java, etc… don’t have built in support for true multiple inheritance). The .compose() method has very little in common with that approach.

          For more detail on the patterns stampit uses for multiple, see:
          http://en.wikipedia.org/wiki/Mixin
          http://en.wikipedia.org/wiki/Function_composition_(computer_science)

  1. Thanks again Eric.  I watched the talk you had about a year ago regarding the best practices for using JavaScript and thought your methods were great. The stamp method definitely works.  

Add Comment Register



Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">