This is probably easier to demonstrate than to explain. Let’s start with a simple parent ‘class’:
1 2 3 4 5 6 7 8 9 10 11 12 13
Parent object has a private variable,
myPrivateVariable, that sits in the closure of the constructor function. Privileged methods in the same scope have the ability to manipulate it. Instances of
Parent also come with an array,
myArray. So far, so straightforward. Let’s inherit from it via prototype:
1 2 3 4 5 6 7 8
So we have two new child instances, Bob and Mary. This seems fine so far. But danger awaits:
1 2 3 4 5
Flipping the private variable on Bob has changed the private variable on Mary. And adding elements to Mary’s array has changed Bob’s. Try it in the console if you like.
Why it happens
Bob and Mary both have a delegate object, their prototype, which was set on the
Child constructor. That object is shared between them. Therefore, there’s only one closure around the privileged methods on that object. There’s only ‘one’
flipPrivateVariable and it’s flipping only one private variable. So if you expect your child objects to inherit a parent’s handling of private state, you’re going to be disappointed.
As for the array access, this happens because when you set elements on the array, you’re not shadowing the property. Normally, assignments to prototype properties causes them to be overwritten with a value ‘local’ to the specific object. But you’re not overwriting anything when you access the array members – you’re adding and overwriting the array’s members. This happens with anything implemented as a reference value – and that includes plain objects.
Why this is insidious
The problem with this behaviour is that it’s unintuitive, and won’t raise bugs until you have more than one instance of an object. This might happen quite late in a project, so even if the enhancement is raised with the original developer, they’ll be trawling through unfamiliar code. And the refactor they’ll have to do will be much harder as a result.
What can I do about this?
There are a couple of obvious solutions.
Keep using prototypes, but keep reference values off prototypes and re-apply constructors
The solution is twofold:
function.apply(this)to call the parent constructor in the body of the child constructor. This will create a new closure and a new set of privileged methods within the closure of the child object, so that are unique to the instance. This technique is documented by Ben Nadel.
So for our code, this would mean:
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
It works, but I think it’s a bit awkward myself. You have to warn your peers not to shove reference values onto prototypes, or as public fields of anything they might wish to later extend. Maybe this is feasible if you’re a small team, but I wouldn’t take my chances. As I’ve explained above, if one misuse gets through (easy when the bug is so subtle), you might not spot the problem until a point where refactoring will be much, much harder.
Ditch the prototypes, and just use parasitic inheritance
This is my favoured solution. We lose the prototypes altogether and use a much simpler approach to inheritance – parasitic constructors.
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
Every instance of
child has its own instance of
parent, which it decorates with its own fields and finally returns. With no shared parent objects, there can be no shared state bugs.
These functions act a lot like normal constructors (you can call them safely with the
new operator), although they do lose you the ability to use
instanceof to determine an object’s class family. You can still use
instanceof to determine Bob and Mary’s primitive types (string, object, function, etc.), but not whether they’re an instance of
Parent. Personally, I’ve probably only ever used instanceof a handful of times – in most projects I don’t deal with objects of unknown pedigree, and I prefer duck-typing when I do anyway.
The other caveat is that because all the properties are duplicated, parasitic inheritance can potentially be memory inefficient if there are lot of large fields. This isn’t normally the case unless you have a lot of objects with complex methods, however. If really needs be, it might be ameliorated by putting large functions on the Parent into some kind of closure surrounding it, and calling them by delegates. For example:
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
You should only bother trying this if really necessary, though. We all know that premature optimization is the root of all evil
In any case, parasitic inheritance is my favoured approach, because it requires a lot less education and policing than trying to use prototypes safely. There seems to have been an obsession with prototypes in the front end community as of late, amounting to something of a war on constructors, but few of the discussions out there seem to recognize the exact nature of prototypes and what they mean for extensible classes with private state. Make sure that you and your team, however, do.