JavaScript adopts a radically different take on inheritance from what one is perhaps
used to coming from an object oriented, statically typed language world such as
C++, C# or Java. In the typical OO static language, inheritance is applied to "classes"
which are essentially templates for how an object instance should be laid out in
memory at runtime. JavaScript however is weakly typed, i.e. pretty much everything
is an "object". How will inheritance work out in a weakly typed dynamic language
such as JavaScript? Enter "prototypal inheritance"!
Prototypal Inheritance
Somewhat counter-intuitively, inheritance in JavaScript applies to object instances
instead of types. I know that sounds kind of weird but once we see how it works,
it actually turns out to be quite elegant. Every object has a hidden default member
called its "prototype". The prototype of an object is a simple reference to another
object which in turn has its own prototype reference to yet another object. Here's
a picture illustrating this:
Here, object1's prototype points to object2 and object2's
prototype points to the prototype of the JavaScript Object type (part
of the JavaScript library). The Object type's prototype does not
in turn point to yet another object and is always null thereby making
it the root object in the inheritance hierarchy.
Whenever you attempt to access a member field or method on an object the JavaScript
runtime attempts to locate the member directly on the object. If the lookup fails
then it attempts to locate the member in the object's prototype and continues
to walk the prototype chain till the member is found (if the member ends up never
being found then you get a syntax error or an "undefined" reference depending on
the context). Here's a pseudo-implementation of member lookup implemented in JavaScript:
//
// dereferences the member identified by "memid"
// if it exists directly on the object; returns
// "null" otherwise
//
Object.prototype.getOwnProperty = function (memid) {
if (this.hasOwnProperty(memid))
return this[memid];
return null;
};
//
// lookup member property/method called "memid" on object
// "obj"; walks the prototype inheritance chain till member
// is found; returns "null" if member is not found
//
function lookup(obj, memid) {
var mem = obj.getOwnProperty(memid);
while (!mem) {
obj = Object.getPrototypeOf(obj);
if (!obj)
break;
mem = obj.getOwnProperty(memid);
}
return mem;
}
So how does prototypal inheritance work?
So how exactly do you do prototypal inheritance? The basic idea is that new objects
are created by copying the structure of an existing prototype object (see prototype creational design pattern). Here's another bit
of JavaScript pseudo-code that shows how this might work:
var person_proto = {
name: "no name",
age: 0
};
var o = {};
o.prototype = person_proto; // this won't actually work!
Please note that the above actually won't work the way you intend it to if you run
it (it will simply create a new property called "prototype" on "o"!).
The object "o"'s prototype will not get set. In fact JavaScript does
not provide a mechanism to assign a value to an object instance's prototype (Mozilla
and WebKit browsers do however permit this via a special extension property named
__proto__ - but this is not a part of the ECMAScript standard and should
not be relied upon). Having said that, in essence, what has been given above is
what happens when you inherit prototypally. Here, "o" is a new object
whose prototype is a reference to another object (person_proto). When you access the name
property like so:
print(o.name);
The JavaScript engine would actually resolve the "name" property on the object's
prototype since the object itself does not have a property called "name". But since
the JavaScript language does not allow one to assign the prototype on an object
instance how can we achieve this sort of inheritance in a standards compliant manner?
Douglas Crockford
has discussed precisely this in his blog post on prototypal inheritance. His solution basically
takes advantage of the fact that while you cannot assign the prototype of an object
instance you can in fact assign the prototype on a Function object.
Here's a utility routine that allows us to inherit prototypally from another object.
function inherit(o) {
function F() {}
F.prototype = o;
return new F();
}
And here's some code that shows how we'd use this function to inherit from the
person_proto object defined in the previous snippet:
var gender_person = inherit(person_proto);
gender_person.gender = "f";
Note that I have also extended the gender_person object by tacking
on the gender property to it. Imagine that I wish to inherit another
object from gender_person and add a "married" boolean
member field to it. Here's how I'd go about it.
var marital_person = inherit(gender_person);
marital_person.married = false;
Here's the inheritance chain depicted pictorially:
Note that the inheritance relationship is completely between object instances and
not types. Also note the following aspects of this style of programming:
- Each "inherit" operation is essentially an object instantiation.
- Each "inherit" seems to be always accompanied with the definition of new members on
the new object.
ECMAScript
version 5 therefore adds a new function called Object.create that does exactly this. Here's
the same example done using Object.create instead of our homebrewed
inherit.
//
// create basic person prototype object; this
// object's prototype will be equal to Object.prototype
//
var person_proto = {
name: "no name",
age: 0
};
//
// inherit from "person_proto" and add a new member
// field called "gender"
//
var gender_person = Object.create(person_proto, {
gender: {
value: "f",
writable: true,
enumerable: true,
configurable: true
}
});
//
// inherit from "gender_person" and add a new member
// field called "married"
//
var marital_person = Object.create(gender_person, {
married: {
value: false,
writable: true,
enumerable: true,
configurable: true
}
});
for(var i in marital_person)
print(sprintf("marital_person.%s = %s", i, marital_person[i]));
Here's the output we get when we run this snippet (you can try it in the Eval console yourself if you like - just make sure that
you're running a browser that has support for ECMAScript 5 - which is most modern
browsers).
marital_person.married = false
marital_person.gender = f
marital_person.name = no name
marital_person.age = 0
Its interesting to observe that when you use for...in to reflect on
an object it seems to walk the prototype chain bottom up. In the output above, it
printed the properties on marital_person first, then gender_person
and finally proto_person. This prototypal nature of the language is
what enables you to do powerful things such as adding new members to even core library
types such as Object, String, Array etc.
When you add a new trim function to String.prototype for
instance, it automagically shows up not just on new string objects created henceforth
but on all string instances that are alive at that point in time! This is made possible
because during the actual member resolution process the runtime walks the prototype
chain to locate it before giving up.
But I want my new!
What I have described so far is the way inheritance was designed to work in JavaScript.
Desiring to cater to developers who are familiar with static languages such as Java
(which was really taking off at the time JavaScript was introduced), Netscape decided
to make the unfortunate choice of simulating classical object instantiation and
type inheritance behavior by introducing syntactic sugar (or maybe it should be called syntactic salt!)
that hid the prototypal nature from the programmer. The end result is a construct
that ends up being not quite classical inheritance while deliberately misleading
the developer on what's actually happening under the covers! Its fairly common to
see code such as the following today:
function Person(n, a) {
this.name = n;
this.age = a;
}
var p = new Person("Foo", 10);
Its interesting to see what is actually happening under the covers when you "new"
up an object. Here's the last line of the snippet given above "expanded" to the
actual instructions being run under the covers in pseudocode form.
var p = {}; // create empty object
p.prototype = Person.prototype; // assign prototype (as before
// this won't actually set the
// prototype)
Person.call(p, "Foo", 10); // run constructor on object
Briefly, an empty object gets created and its prototype gets assigned to Person's
prototype and the constructor function is invoked using the new object as its context. It is
self-evident that there really isn't a "type" called "Person". The language just
allows the developer to pretend like there is! In fact it even provides an "instanceof"
operator that you can use to verify if an object is an instance of a particular "type". Here's how
you use it
function Person(n, a) {
this.name = n;
this.age = a;
}
var p = new Person("Foo", 10);
print(p instanceof Person); // prints "true"
What the operator does is to check if the prototype of the constructor function (Person in the snippet above) figures in the object's
prototype chain. Here's a possible naive implementation of the "instanceof" operator as a
JavaScript function with some sample usage:
function instanceOf(o, t) {
if(typeof(t) !== "function")
throw "Pass a Function object for type name.";
if(typeof(o) !== "object")
throw "Object must not be a primitive type.";
var proto = t.prototype;
var op = Object.getPrototypeOf(o);
while(op) {
if(proto === op)
return true;
op = Object.getPrototypeOf(op);
}
return false;
}
function Person(n, a) {
this.name = n;
this.age = a;
}
var p = new Person("Foo", 10);
print(instanceOf(p, Person)); // prints "true"
With that we conclude our brief review of how inheritance was designed to work in
JavaScript. I have deliberately not discussed how we can simulate classical inheritance
patterns in JavaScript though that is very much possible. Douglas Crockford has
covered this topic in this article but he ended up adding the following footnote
to it - presumably many years after he'd written that article (emphasis mine):
I have been writing JavaScript for 8 years now, and I have never once found need
to use an uber function. The super idea is fairly important in the classical pattern,
but it appears to be unnecessary in the prototypal and functional patterns. I now
see my early attempts to support the classical model in JavaScript as a mistake.