Fluent JavaScript – Three Different Kinds of Prototypal OO
On Feb 11, 2013 JavaScript 25 25 Comments Tags: JavaScript, objects, oo, prototypal oo, prototypesIn order to claim fluency in JavaScript, it’s important to understand how JavaScript’s native inheritance capabilities work. This is an often neglected area of JavaScript writing and learning, but understanding it can be dramatically empowering.
JavaScript is one of the most expressive programming languages ever created. In particular, its combination of delegate prototypes, runtime object extension, and closures allow you to express three distinct types of prototypes in JavaScript. Let’s take a closer look at each of these.
Delegation / Differential Inheritance
A delegate prototype is an object that serves as a base for another object. When you inherit from a delegate prototype, the new object gets a reference to the prototype. When you try to access a property on the new object, it checks the object’s own properties first. If it doesn’t find it there, it checks the prototype, and so on up the chain until it gets back to Object.prototype.
Method delegation is a fantastic way to preserve memory resources, because you only need one copy of each method to be shared by all instances. It’s also a great way to add capabilities at runtime to all objects which share a particular prototype.
There are a couple of ways to set up that relationship in JavaScript. The one you’re likely to see in a lot of books goes something like this:
|
1 2 3 4 5 6 7 8 9 |
function Greeter(name) { this.name = name || 'John Doe'; } Greeter.prototype.hello = function hello() { return 'Hello, my name is ' + this.name; } var george = new Greeter('George'); |
See JavaScript Constructor Functions vs Factory Functions and Stop Using Constructor Functions in JavaScript for my thoughts on why you should ignore this technique. I present it here only because it’s likely to be a familiar point of reference.
I prefer this:
|
1 2 3 4 5 6 7 8 |
var proto = { hello: function hello() { return 'Hello, my name is ' + this.name; } }; var george = Object.create(proto); george.name = 'George'; |
The one major drawback to delegation is that it’s not very good at storing state. In particular, if you try to store state as objects or arrays, mutating any member of the object or array will mutate the member for every instance that shares the prototype. In order to preserve instance safety, you need to make a copy of the state for each object.
Cloning / Concatenative Inheritance / Mixins
Prototype cloning is the process of copying the properties from one object to another, without retaining a reference between the two objects. Cloning a great way to store default state for objects. This process is commonly achieved by methods like Underscore’s .extend(), or jQuery’s .extend():
|
1 2 3 4 5 6 7 |
var proto = { hello: function hello() { return 'Hello, my name is ' + this.name; } }; var george = _.extend({}, proto, {name: 'George'}); |
It’s common to see this style used for mixins. For example, Backbone users can make any object an event emitter by extending from Backbone.Events:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var foo = _.extend({ attrs: {}, set: function (name, value) { this.attrs[name] = value; this.trigger('change', { name: name, value: value }); }, get: function (name, value) { return this.attrs[name]; } }, Backbone.Events); |
Closure Prototypes / Functional Inheritance
Closure prototypes are functions that can be run against a target object in order to extend it. The primary advantage of this style is that it allows for encapsulation. In other words, you can enforce private state. Douglas Crockford called this style “Functional Inheritance” in his book, “JavaScript: The Good Parts”. It looks something like this (Foo, like above, with private attributes):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var model = function () { var attrs = {}; this.set = function (name, value) { attrs[name] = value; this.trigger('change', { name: name, value: value }); }; this.get = function (name, value) { return attrs[name]; }; _.extend(this, Backbone.Events); }; model.call(george, 'Top secret'); george.on('change', function (e) { console.log(e); }); george.set('name', 'Sam'); // Object {name: "name", value: "Sam"} |
This is all well and good, but there’s an awful lot of jumping through hoops if you want to combine the techniques — so I wrote a little library to jump through the hoops for you. It’s called Stampit.
Stampit
Create objects from reusable, composable behaviors.
Features
- differential inheritance, aka delegation (for methods),
- cloning, aka concatenation/exemplar prototypes (for state),
- functional / closure prototypes (for privacy / encapsulation)
What’s the Point?
Prototypal OO is great, and JavaScript’s capabilities give us some really powerful tools to explore it, but it could be easier to use.
Basic questions like “how do I inherit privileged methods and private data?” and “what are some good alternatives to inheritance hierarchies?” are stumpers for many JavaScript users.
Let’s answer both of these questions at the same time. First, we’ll use a closure to create data privacy:
|
1 2 3 4 5 6 |
var a = stampit().enclose(function () { var a = 'a'; this.getA = function () { return a; }; }); |
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.
Let’s see if that worked:
|
1 2 |
a(); // Object -- so far so good. a().getA(); // "a" |
Yes. Got it. In both of these instances, we actually created a brand new object, and then immediately threw it away, because we didn’t assign it to anything. Don’t worry about that.
Here’s another:
|
1 2 3 4 5 6 |
var b = stampit().enclose(function () { var a = 'b'; this.getB = function () { return a; }; }); |
Those a‘s are not a typo. The point is to demonstrate that a and b‘s private variables won’t clash.
But here’s the real treat:
|
1 2 3 4 5 6 |
var c = stampit.compose(a, b); var foo = c(); // we won't throw this one away... foo.getA(); // "a" foo.getB(); // "b" |
WAT? Yeah. You just inherited privileged methods and private data from two sources at the same time.
But that’s boring. Let’s see what else is on tap:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// Some more privileged methods, with some private data. // Use stampit.extend() to make this feel declarative: var availability = stampit().enclose(function () { var isOpen = false; // private return stampit.extend(this, { open: function open() { isOpen = true; return this; }, close: function close() { isOpen = false; return this; }, isOpen: function isOpenMethod() { return isOpen; } }); }); // Here's a mixin with public methods, and some state: var membership = stampit({ add: function (member) { this.members[member.name] = member; return this; }, getMember: function (name) { return this.members[name]; } }, { members: {} }); // Let's set some defaults: var defaults = stampit().state({ name: 'The Saloon', specials: 'Whisky, Gin, Tequila' }); // Classical inheritance has nothing on this. No parent/child coupling. No deep inheritance hierarchies. // Just good, clean code reusability. var bar = stampit.compose(defaults, availability, membership); // Note that you can override state on instantiation: var myBar = bar({name: 'Moe\'s'}); // Silly, but proves that everything is as it should be. myBar.add({name: 'Homer' }).open().getMember('Homer'); |
IMO, mixins work best when added to prototypes.
One advantage of constructors is that many engines optimize for them. Even though I really like the prototypal approach [1], I’ve resigned myself to fitting in and now only use constructors. They will become further entrenched in ECMAScript 6, via classes.
[1] http://www.2ality.com/2011/06/prototypes-as-classes.html
Axel – I agree that mixins work best when added to prototypes. You can extend a new prototype like this:
`Object.create(_.extend({}, aProto, someMethods));There really isn’t a big performance difference. In fact, there isn’t much different going on functionally, at all, except that the constructor has to jump through **extra hoops** to interpret
newand set upthis. If you follow best practices and do theinstanceofcheck in your constructors, that’s another step (and potentially another method call), as well. In other words, while they can optimize away, they’re really just trying to figure out how to speed up what is inherently a slower process than just running a normal function. Throw the common block ofthis.foo = that;into the mix (vs something likereturn Object.create(someProto);), and you have a recipe for slow.See JavaScript Constructor Functions vs Factory Functions.
As for that prototypes-as-classes link — what they’re saying is that the new class keyword in JavaScript is going to work a lot more like the methods I prescribed in this article. One major difference — I do not advocate building deep inheritance chains. Keep it as flat as you can.
Thanks for your feedback!
Here is a jsPerf showing the performance difference:
http://jsperf.com/object-create-vs-constructor-vs-object-literal/49
Spoiler: constructors are 28 times faster than the fastest usage of Object.create.
Looks pretty bad if all you look at is prototype assignment, right? But that finding gets flipped on its head if you look at instance property assignment (the bit that actually varies between objects). See this factory vs constructor perf.
It’s pretty easy to tell the story you want to tell, just by being selective about what you test. And since
Object.create()is a native method, when we see more use in the wild, it will get the same type of optimization attention that constructors got — and since the operation is inherently less complicated, it could potentially be optimized even better (it doesn’t have to mess withnewor set upinstanceof, or branch for different return behaviors in the constructor function).There is no Object.create anywhere in this jsPerf you posted. Returning an object literal is of course faster than constructor usage, which is still significantly faster than Object.create.
As you say, after more usage in the wild the optimization could improve and it could “potentially” be optimized even better but in today’s real-world JavaScript, constructors are much faster than Object.create.
All fair points, but I’d be careful not to put too much weight on this aspect of it, because:
1) That particular difference is very likely to get optimized away in the engines — possibly soon, and
2) If you really want the best possible performance, you should be using factories that return object literals, instead of constructor functions or Object.create() — but this is unlikely to become a bottleneck in a typical JavaScript application. Far more likely culprits will be your choice of algorithms, data structures, I/O, DOM manipulations, or page reflows.
It’s good to be aware of performance implications, but it’s a bit silly to point at perf results and conclude that the technique is never useful, simply because there’s another technique that happens to have a better performance profile. Sometimes a slight perf tradeoff is worth it, because of the gains in clarity or flexibility in your code.
There’s actually a **HUGE** performance difference between cloning objects using constructors, and cloning objects using Object.create. However, the performance of all other operations on the object after you’ve created it have pretty much the same performance. You can see a test case for this here: http://jsperf.com/object-creation-access/2 (That test case is almost two years old now, and I really should have separated the operations since it’s a pain to compare in this state. But you can use the
Tableview).Anyways, from a practical standpoint, Object.create is likely to perform okay in all situations even with the unfair overt-optimisation of creation through constructors. For example, my library for property-based testing in JavaScript (a “port” of ScalaCheck/QuickCheck) is prototypes all the way down, using Object.create exclusively, and even though every possible operation clones the previous object for immutability, it still has an acceptable performance (also note that this makes the delegation chain *huge*). The architecture is also much different than most other JS applications because you’re probably not creating a thousand of objects every millisecond — unless you’re writing a game engine.
So, Object.create’s performance is not great, but it is acceptable today for most stuff. Ditching it for constructors just because they’re faster now isn’t really a good argument.
If you’re making a game engine and generating rapid-fire bullets or something at the tens-of-thousands-per-second rate, constructors still don’t give you best case performance. Object literals do. Unless each object has a lot of big methods attached to it, in which case I’d say your object architecture needs re-thinking. =)
So if you’re optimizing for speed, you still don’t want to use constructors.
Am I missing something, or the only way to actually have a “inheritance” chain is by using a constructor or Object.create which is basically a empty constructor ??
See JavaScript Constructor Functions vs Factory Functions.
Just few days ago I was looking for some cleaner and more flexible solution to plain classic-like inheritance via prototypes, and now I’ve found one! Really like it, will try to use in the upcoming project. Thanks!
Thanks! Let me know how it goes.
I really like this approach and Stampit seems very convenient and elegant.
The one thing that’s always bugged me about javascript inheritance is that there’s no built-in “super”-like keyword, even though other purely prototypal languages like self and Io have this feature. It makes it really difficult to method overrides with more than 1 level of inheritance.
I don’t suggest that you use more than one level of inheritance, and I find that, while I do use method overrides and prototypal inheritance together, the only thing I do that’s like super is the stacking of functional inheritance that happens when you compose multiple stamps which implement closures (the
.enclose()feature).I find that trying to use something like super tends to get error prone and hard-to-debug quickly.
You can implement “super” in your js lib. See Backbone-Super or jquery.inherit
I have no intention of implementing
super. Martin Fowler thinks super is a code smell, and I agree. I have a strong distaste for the tight coupling it requires between the child and the parent. As soon as you give the child instance a handle to the thing its inheriting from, you’ve broken encapsulation, and reintroduced the same brittle architecture we were trying to get away from in the first place when we switched to prototypal OO.Instead of super, there are a million things you can do: facade, dependency injection, “abstract interface”, etc… The last one presents something interesting (and the one Martin Fowler recommends). You can implement base features and then provide hooks for the “concrete” instances. I put quotes around those of course because every object in JavaScript is a concrete instance, but in this case, the “abstract” version defines an exemplar interface that the concrete object can implement using hooks — the hooks are the bits you’re supposed to override, whereas the interface bits should not be overridden.
For example, if you were building a media player, you could define
.play(),.pause(), and.volume()methods on the interface. Lets say all of these operations require some business logic that must be implemented regardless of the tech — for instance, the.play()method needs to check to see if the user is authorized to play the media.These methods can be set up to call hooks:
.techPlay(),.techPause(), and.techVolume(). Now you create anhtml5Playerand aflashPlayer. They return objects that define the.tech*methods. Usingstampit, you could inherit from both the base media player and a tech player to create your working player instances.I would argue sternly that defining yet another level of inheritance which needs to override those same methods again is probably a bad idea. With each level of method override you add, you increase the complexity and opportunity for trouble exponentially.
Besides, it’s more likely that what you really need to do is emit an event from
.play()that any object can listen for in order to implement additional behaviors or manage cross-cutting concerns.I am (was?) in the same boat as Max. Will definitely be trying this out. Thanks!
Great find! …nice read.
I recently did something similar in search of a better way – need was secondary at the time. My solution was far more convoluted, less flexible and incomplete (yet fully tested.) So this is timely for me. I will go back and see where I went astray; but I may simply scrap it for your small lib instead. Thanks for the article and coce.
Greg
This is my second go at a library like this. It’s easy to second guess other people’s design choices, but much harder to crank out a solid design yourself. My first attempt had some good things going for it, but ultimately fell short of the mark, and it was more complicated than it needed to be. I think this one hit the sweet spot. At least for me. Let me know what you think if you decide to use it. =)
Great article.
Also, this bit in one of your comments should be repeated over and over again:
“It’s good to be aware of performance implications, but it’s a bit silly to point at perf results and conclude that the technique is never useful, simply because there’s another technique that happens to have a better performance profile. Sometimes a slight perf tradeoff is worth it, because of the gains in clarity or flexibility in your code.”
To the new and the old guys. To ourselves too.
Thiago, I agree with you. In fact, I wrote another blog post in response to the perf critiques here called “You’re Optimizing the Wrong Things”.
Eric,
Stampit sounds interesting, and you make some reasonable arguments for writing JS code in this style (I’m new to JS and trying to figure out how best to structure my project and currently trying to get my head around Closure), but it needs real documentation rather than just some simple examples.
The readme says “you can chain methods…” and provides an example that doesn’t tell me what methods is or why I would want to chain them.
I don’t have any familiarity w/ _.extend(), $.extend(), and ES6 Object.mixIn(), so referencing them as the sole documentation for methods and state isn’t very helpful.
I’m looking forward to understanding more though.
Hi Mike,
Very good point. I have updated the Stampit documentation. Does that help?