Introduction
In JavaScript: The Good Parts, Douglas Crockford disapproves of Pseudoclassical Inheritance in favor of Functional Inheritance. However, a quick survey of JavaScript resources such as Mozilla Developer Network(MDN) and JavaScript Tools/Libraries such as Google Closure, CoffeeScript, YUI3, and Jasmine indicate that Pseudoclassical pattern seems to be quite a popular inheritance pattern.
Drawbacks of Functional Inheritance
Michael Bolin who was a core contributor to Google Closure presents a strong case for Pseudoclassical Inheritance in his blog post Inheritance Patterns in JavaScript. In his post he discusses drawbacks for using Functional Inheritance and also addresses Douglas Crockford’s concerns for Pseudoclassical Inheritance.
Here is an example of Functional Inheritance from Douglas Crockford’s book:
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 |
|
and the same “classes” modeled using Pseudoclassical Inheritance:
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 |
|
Main Drawbacks
I would recommend reading Michael Bolins Inheritance Patterns in JavaScript for an in depth explanation of these drawbacks. Note that some of his points are made in respect to Google Closure.
Instance of types take up more memory
- Every time
mammal()
is called, two new functions are created (one per method of the type). Each time, the functions are basically the same, but they are bound to different values. These functions are expensive because each is a closure that maintains a reference for every named variable in the enclosing function in which the closure was defined. This may accidentally prevent objects from being garbage collected, causing a memory leak. - This is not a concern when
Mammal
is called because of how it takes advantage of prototype-based inheritance. Each method is defined once onMammal.prototype
and is therefore available to every instance ofMammal
. This limits the number of function objects that are created and does not run the risk of leaking memory
- Every time
Types cannot be tested using instanceof
- From MDN, the
instanceof
operator tests whether an object has in its prototype chain theprototype
property of a constructor.object instanceof constructor
- Since prototypes are not used for implementing inheritance for
cat()
, there is no appropriate constructor to use for instanceof
- From MDN, the
- Encourages adding properties to Function.prototype and Object.prototype
- In his book, Crockford augment’s the prototype to assist in creating superclass methods
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Adding methods to the prototype is dangerous, especially because the
for in
construct includes properties added to the prototype.Makes it impossible to update all instances of a type
- You need to individually update each instance.
- In Pseudoclassical Inheritance, updating all instance of a type is as simple as updating properties or methods on the prototype.
Closure-related/Other Drawbacks
- Methods cannot be inlined
- Methods in the functional pattern are often bound to variables that cannot be referenced externally, so there is no way for Closure Compiler to rewrite method calls in a manner that eliminates the method dispatch
- Superclass methods cannot be renamed(or will be renamed incorrectly)
- Method renaming from JavaScript minifiers can use issues
- Results in an extra level of indentation
- Coding preference
- Naming newly created objects is awkward
Naive Implementation of Pseudoclassical Inheritance
Here is an example of implementing Pseudoclassical Inheritance from JavaScript Garden.
The main idea is to create a dummy instance of the parent Foo
via new Foo
and also call the parent constructor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
While this way of implementing Pseudoclassical Inheritance is not wrong per se, there is a little awkwardness to how the child’s prototype (Bar.prototype
) is being set.
Imagine the case, where the parent constructor takes in a couple parameters. It would be strange to invoke the new Parent()
with dummy parameters because the instantiated object is a dummy instance. Additionally, the superclass’s constructor may have side effects (i.e. anything instance properties set in the superclass constructor get added to the prototype)or do something that is computationally intensive. Therefore, since the object that gets instantiated for the prototype is usually just a throwaway instance, you don’t want to create it unnecessarily.
General Implementation of Pseudoclassical Inheritance
To improve on the naive implementation of Pseudoclassical inheritance, we can create a dummy constructor, set it’s prototype to the prototype of the parent, and create a dummy instance that is used as the prototype of the child. Same as before, we also need to reset the prototype.constructor of the child to child(because the prototype needs to reference the appropraite constructor) and call the parent constructor in the child.
Use a dummy constructor when creating the object used for the prototype because superclass’s constructor may have side effects or do something that is computationally intensive
We can improve the the previous Foo Bar example like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Pay close attention to the use of the native Object.create method which creates a new object with the specified prototype object and properties.
The
Object.create()
method creates a new object with the specified prototype object and properties.
Here is a simple Object.create polyfill from Crockford’s website.
1 2 3 4 5 6 7 |
|
And an optimized polyfill from MDN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Pseudoclassical Inheritance in the wild
Based on our understanding of why and how to implement Pseudoclassical Inheritance, now we’ll take a look at how this pattern is being used for large JavaScript Libraries.
Google Closure
Closure is a set of individual JavaScript tools that are also designed to help developers build complex web applications, and is used by Gmail, Google Maps, and Google Docs.
Inheritance in Google Closure is implementance via Pseudoclassical Inheritance. Google closure also adds a base
method to the child constructor to expose
a covenient way to invoke parent methods.
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 50 51 |
|
CoffeeScript
In addition to using Pseudoclassical inheritance for the underlying implementation of CoffeeScript, Jeremy Askenas also discusses inconviences to libraries that try to provide a cleaner syntax for classical inheritance.
JavaScript’s prototypal inheritance has always been a bit of a brain-bender, with a whole family tree of libraries that provide a cleaner syntax for classical inheritance on top of JavaScript’s prototypes: Base2, Prototype.js, JS.Class, etc. The libraries provide syntactic sugar, but the built-in inheritance would be completely usable if it weren’t for a couple of small exceptions: it’s awkward to call super (the prototype object’s implementation of the current function), and it’s awkward to correctly set the prototype chain.
The crux of Jeremy’s implementation of Pseudoclassical inheritance is the __extends
function.
An interesting part of the implementation is the for loop which copies static methods from the parent
onto the child.
1 2 3 |
|
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 |
|
Jasmine
Jasmine is a behavior-driven development framework for testing JavaScript code. In Jasmine, inherit is attached to the utility object util.
1 2 3 4 5 6 |
|
YUI3
YUI is a free, open source JavaScript and CSS library from Yahoo for building richly interactive web applications.
The method in YUI for inheritance is Y.extend
used like so Y.extend(Programmer, Person);
where a Programmer inherits from Person.
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 |
|
Y.Object
used within Y.extend
is essentially a wrapper
for Object.create
with a fallback. The fallback wraps the dummy constructor around an Immediately Invoked Function Expression(IIFE),
so that the dummy constuctor can be reused.
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 |
|
Conclusion
I hope this article sheds light on the advantages of using Pseudoclassical Inheritance over Functional Inheritance and clarifies how to implement Pseudoclassical Inheritance in an optimal manner.