After having read Eric Elliott's Fluent JavaScript article, I was and still am toughtful about the way to play with instance prototypes.
On one side, you have the extending inheritance...
var B = function() {} ;
B.prototype = new A() ;
Or the new way of doing it...
B.prototype = Object.create(A.prototype) ;
And on the other side, you have the mixin, creating a mix of multiple prototypes that will be inherited to instances.
var B = function() {} ;
// Basic mixin from A
for (var m in A.prototype) B.prototype[m] = A.prototype[m] ;
In his article, Eric Elliott encourages us to avoid extending types but to compose (mixin) sort of components objects/prototypes instead (see also Stampit on github). I totally agree with that.
However, it seems to break one major feature (and philosophy) in JavaScript : Modularity. What I mean is the fact that altering prototypes will be automatically available for all instances by references.
Here is a basic example of the prototype inheritance :
var A = function() {} ;
A.prototype.fn = function() { return 1 ; } ;
var a = new A() ;
a.fn() ; // 1
A.prototype.fn = function() { return 2 ; } ;
a.fn() ; // 2
And now with an extended prototype :
var B = function() {} ;
B.prototype = Object.create(A.prototype) ;
var b = new B() ;
b.fn() ; // 2, inherited from A.prototype
Okay this is working well, however we can't inherit from multiple prototypes ; plus classical inheritance is generally avoided.
The other solution would be mixin then :
var D = function() {} ;
// Let's imagine a mixin function mixing D with some prototypes...
mixin(D, A.prototype, C.prototype, R.prototype) ;
var d = new D() ;
d.fn() ; // 2, inherited from A.prototype ? Let's see...
A.prototype.fn = function() { return 3 ; } ;
d.fn() ; // 2, what a shame...
Indeed, mixin only copy all functions from a prototype to another. This means there is no more references between those types, and thus modularity looks broken here (also tested with Stampit).
I'm now thinking of a tweaky solution to keep references to original prototype objects and not only the functions inside.
What about cascading inheritances ?
Not like
D <- C <- B <- A
, as it will alter B and C and looks restrictive.But like
D <- (((p <- A) <- B) <- C)
, where p is a private prototype reserved for D only. Neither B nor C will be altered, only p will be a straight cascade inheritance. D will have all methods from the listed prototypes, and if we want to inherit from another one, just restart the process from zero, likeD <- ((((p <- A) <- B) <- C) <- R)
.
The only drawback I see would be around performances. Each time we add a new inheritance, we'll have to restart the whole process : Indeed, after inheriting, we add new functions to the prototype (or override some), and this must be always at the end of the process, thus after R inheritance :
Before :
D <- ((((p <- A) <- B) <- C) <- {})
where {} is the set of new/overriding functions.After :
D <- (((((p <- A) <- B) <- C) <- R) <- {})
, and not... <- {}) <- R)
.
Would it be interesting to have a little library for that ? I would be up for doing this, allowing multiple prototype inheritances.
So... what do you think about this all ?