If you have been programming in Java and C++ for a while and are used to thinking about problems in terms of classes and inheritance, you may find yourself struggling with Javascript since it has only objects. This post is to – a) provide you with a perspective on why programming with only objects is powerful and b) show you how to translate the class-based concepts you’re used to thinking in into the Javascript world.
The Javascript object system comes from the language Self which pioneered object oriented modeling using prototypes, along with many dynamic compilation techniques that later went into the HotSpot JVM. The motivation for destroying the class-object dichotomy is that working with classes forces you to think about object categories well before you’ve really understood the problem domain. The claim by the Self folks is that your approximate and often downright invalid classes get frozen in over time and the system becomes harder to adapt to changes in understanding about the problem being modelled.
So, what are classes? … really?
Classes as machines
When you write a class in a language like Java or C++, you’re in essence writing a recipe for making objects with certain properties and behaviours. The compiler then translates this recipe into a machine that can make such objects whenever you need them at runtime. If you ignore the compilation step, you can think of a “class” itself as the machine that produces objects, without loss of accuracy. It is this operational view of classes that offers the key to reusing class-based modeling techniques in an objects-only language like Javascript.
So, a class is a machine — in goes some parameters available only at runtime and out comes an object with the properties and behaviour as dictated by the recipe. That thinking can be directly modelled as a function that takes some parameters and produces an object with the right properties and behaviour.
1
|
|
In what follows, I’ll omit the parameters for simplicity of presentation and you can always add them back later. So we have -
1
|
|
This gives a “first order” view of classes. Though a useful way of thinking
in itself, you soon hit a modelling wall the moment you want another class
that is only slightly different from one you already wrote. You cringe at
having to copy-paste code to produce make objects of this slightly different
variety. What you want is some way to code up only the incremental difference
from MyClass
.
Combining classes
One way out is to write a function that can combine the properties and behaviour
of two given objects – call it blend
. So that will let you write -
1
|
|
.. where NewBehaviour
is a function that models only what is different
from MyClass
.
The above approach, though valid, is limited by the inability of NewBehaviour
to have any kind of say over the changes to be introduced to the original object.
It would be ideal if NewBehaviour
could first take a look at the object produced
by MyClass
before it makes any changes to it. Therefore, NewBehaviour
is better
written as a function that takes in the object produced by MyClass
and returns
a new object.
1
|
|
NewBehaviour
can now be made arbitarily flexible. In retrospect, we could’ve
also written MyClass
with an extra object parameter, so that the combination
of these two classes itself looks like an “object transformation machine”.
1
|
|
In other words, we model class inheritance in Javascript using function composition. We can now write this notion of inheritance as a “higher order function” in Javascript as -
1 2 3 4 5 |
|
Multiple inheritance and “mixins”
The traditional notion of multiple inheritance froma number of classes C1
, C2
, and
so on in a specific order is then merely repeated application of Inherit
. If we have
an array of classes ClassArr
and we want to make a class that “inherits” from all of them,
all we need to do is –
1 2 |
|
.. which we can abstract again as a function that takes an array of classes and produces a “multiply inherited class”.
1 2 3 |
|
Modify or copy?
Notice that we haven’t placed any constraints on these functions so far with respect to whether they destructively modify the object passed in, or return another object with the necessary properties. Both these are acceptable and you may want to weigh which approach is suitable for you. For instance, creating copies costs memory, but you trade off a one time performance hit for repeated searches up an “inheritance tree”.
Javascript’s “prototype” mechanism
Let us take a simple example of inheritance to make further digging easier to
follow. Say we have an Image
class that can produce objects that know how
to draw
themselves into a context
in portrait mode.
1 2 3 4 5 6 7 8 9 10 |
|
That was easy enough, but say we now want a variant of Image
which produces
objects that draw themselves in landscape mode. i.e., we want -
1 2 |
|
How would we write the Landscape
class?
1 2 3 4 5 6 7 8 9 10 11 |
|
Simple enough? … Oops! We’ve just introduced an infinte draw loop because the landscape object’s draw keeps calling itself!
To solve this, we need to first store away the object’s original
draw
function and use that one within lsDraw
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
This is then the approach to use to model calling the “parent method” within a “child class”.
If you’re modifying a dozen different methods, it can get pretty tiring to save every method away in a variable before calling it within a modified method. This, then, motivates the “prototype chain” mechanism in Javascript.
The “prototype chain”
Javascript provides a way for an object to delegate property and method
access to another object if they aren’t part of itself. Though we can
set up arbitrary numbers of such delegation chains ourselves, the builtin
mechanism serves to ease single inheritance cases. Our Landscape
class
can now be written as -
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Object.create
makes a new object that looks and behaves exactly like obj
,
but now when you add a new property to extendedObj
, the original obj
remains unmofied. Now, we don’t need to save away old method definitions
because the old object will always have them and so we can refer to the old
draw
function as just obj.draw
.
So, what’s up with the weird obj.draw.call(extendedObj, context)
and why
can’t we just write obj.draw(context)
instead?
To answer that, we need to look at what obj.draw(..)
means to Javascript.
It means “draw obj
making use of its properties”. If the draw
function
needs to know the width
of the object to do the drawing, it will access
it will get obj.width
within the draw function. If we later on modify the
width of the extendedObj
, that modified value will not be accessible when
running obj.draw()
because obj
does not know anything about the existence
of extendedObj
.
The solution then, is to tell the obj.draw
function to run, but ask it to
make use of the properties of extendedObj
instead. Now, since extendedObj
otherwise has all the properties of obj
, there is no information loss to the
obj.draw
function. The way we tell the obj.draw
function to do that is to
use the call
method of the Function
object.
1
|
|
The above code, btw, is equivalent to the following -
1 2 |
|
Adaptive methods
Let’s take a second look at the implementation of Image
and the draw
function in particular –
1 2 3 4 5 6 7 8 |
|
If the draw function was originally implemented like above, then it would
be of no use to try to change the context using Function.call
like in
old.draw.call(extendedObj)
because the body of old.draw
always explicitly
refers to the properties of obj
itself.
Oops! It looks like the original
Image
implementation cannot be extended, although it would work fine on its own!
What we want in this case, is for the draw
function to be more generic and
pull properties from an arbitrary object. That is done using the dynamically
scoped this
argument, which, when used within the body of a function, refers
to the object that the caller passed as the first argument to the call
function.
Here is a modified draw
-
1 2 3 4 5 |
|
When this draw function is called with obj.draw.call(extendedObj)
,
the given extendedObj
will substitute this
within the funciton body,
and we get the correct behaviour. When you simply call obj.draw(context)
,
Javascript looks to the left of the draw
name figures out that obj
must be the this
that the caller intended.
1
|
|
Therefore, in a prototype based object system, methods need to be explicitly designed to support extension through prototype chains.
Implementation details
Every object obj
in Javascript has an obj.__proto__
property lurking in the
shadows, which refers to the object that will be looked up should some property
requested of obj
not be found in it. This is called the object’s “prototype”
and Javascript provides some simple special syntax to support making objects
using this prototype chain - the new
operator.
1
|
|
What new
does is exactly what the function make
shown below does -
1 2 3 4 5 |
|
Since the newly created obj
is passed as the context in ClassFn.call
,
the body of ClassFn
can refer to the object whose properties need to
be filled in simply using this
and it need not bother creating a
new object as well, since one is already available.
Note: There is nothing special about ClassFn.prototype
and if you’re
writing your own make
function, you can store a prototype object in
any field of ClassFn
. It just happens to be the property that new
refers to in the bulitin implementation.
Wrapping it all up
Model classes as functions that produce objects.
Model inheritance and “mixins” using higher order functions and function composition.
When implementing methods, be aware of whether you want the method to be extensible via the prototype chain or not. If a method with a bound object in its body is called, it might end up modifying the behaviour of all the objects that it is a prototype of!
Always be aware of what
this
value is being implicated in any function or method call, by translatingfn(arg)
intofn.call(??, arg)
.