For the sake of examples consider the following class hierarchy: Athlete, Footballer, and Defender.
Implementing this with pseudo-classical inheritance would look something like this, assuming the implementation of
Object.extend() from here (I’ve had bad luck with the __super attribute in the past, but let’s assume it works for the moment).
This creates relationships between constructor functions and prototypes that look something like this (if you’ll excuse the ASCII art):
Athlete() ----> Athlete.prototype
Footballer() ----> Footballer.prototype
Defender() ----> Defender.prototype
prototype attributes. Creating a new instance
defender of the Defender class creates these relationships between the prototoypes and the instance:
defender ----> Defender.prototype
This should look familiar; it’s exactly the pattern that Ruby uses for basic inheritance, with the prototype objects representing instances of Ruby’s Class object, and prototype attributes representing Ruby’s
Object#class (horizontal) or
Class#superclass (vertical) attributes. The lookup rules work the same as Ruby as well: start with methods on the instance (in Ruby these would be methods on the instance’s singleton class); if not found, look for a method on the instance’s class; if not found, look on the Class instance’s superclass; repeat until found or you run out of classes/prototypes.
A different approach to creating our classes could look like this (with a nod to Douglas Crockford). There are a few things I prefer about this approach:
- Privates are private. Implementation methods such as #shoot and #feignInjury are hidden from the outside world. The same can be done for instance variables.
- Object definitions have a clearly defined structure: instance variables at the top of the function definition, ending with the declaration and definition of
self; public instance methods next, ending with the return of self; private instance methods at the end, where they belong.
- Object definitions are nicely contained. In the first example the methods are defined at the top level, outside the constructor definition. In this second example everything that defines the class is nicely contained within a single function scope.
- No dependence on, or pollution of the global namespace with, a method such as #extend.
I’ve heard brought up a couple concerns with this style:
- Methods are, necessarily, defined on the object rather than the prototype, which can lead to duplication and inefficiency. This is a fair point, but I have yet to see it cause a problem. I’d be interested to see actual numbers that show how much of a performance hit this causes, given a certain number of object creations. In the meantime, I haven’t noticed a performance problem with code written this way, so I’m inclined to prefer encapsulation over theoretical performance issues.
- Objects defined this way do not properly set their constructor attribute upon creation. Some test methods (notably Jasmine’s
any method) depend on this.
- Closures can be hard to grok.
We’ve recently finished up a reasonably sized project largely using this functional style of object definition, and it worked quite well. I’m sure there’s some reason that it shouldn’t have that I’ve overlooked; I’m curious to hear what that is.