Prototypal chaining (aka inheritance) is something that toke me a couple of years to really understand, simply because you usually don’t use it directly (because a library/framework does it for you) or you don’t need it. I failed a job interview question on that one year ago. And when I recently explained prototypal chaining to a friend of mine, he suggested that I should write something about it. To make it very simple and easy.
I got introduced to Object-oriented programming (OOP) using the C++ and Java languages, with the very infamous Animal
class. JavaScript is a prototypal language, so is a little bit different than Java, C++ or C#. For the next 5 minutes, try to forget what you already know and dive into what follows.
Let’s start by altering the prototype of Object
. (Code tested running SpiderMonkey or inside the Firebug console.)
>>> Object.prototype.foo = "bar"; >>> var a = {}; // or new Object() >>> a.foo "bar" >>> // with Firebug: console.log >>> for(var key in a) { ... print(key); ... } bar >>> "foo" in a true >>> a.hasOwnProperty("foo") false
a
is an Object
that as prototype. The prototype of Object
don’t have any prototypes, Object
is the root element. But the key foo
is a String
that has a prototype that is an Object
. Yes there is a loop.
(Almost) everything in JavaScript is an object and every Object
as a prototype.
>>> a.foo.foo.foo.foo.foo "bar" >>> new Date().foo "bar" >>> 3.14.foo "bar" >>> (42).foo "bar" >>> true.foo "bar" >>> (function(){}).foo "bar" >>> "spam".foo "bar" >>> /^[rR](?:e[gx]){2}p$/.foo "bar" >>> // almost everything >>> undefined.foo TypeError: undefined has no properties >>> TypeError.foo "bar"
I hope this shows that you should never alter Object.prototype
.
Another (evil) example with different native types.
>>> Object.prototype.foo = "bar"; >>> var now = new Date(); >>> now.foo "bar" >>> Date.prototype.foo = "spam"; >>> now.foo "spam" >>> new Object().foo "bar" >>> delete Date.prototype.foo >>> now.foo "bar"
This shows how the language walks from prototype to prototype looking for a key. It’s basically a chain. How to get the prototype of any thing in JavaScript? Usually any object has a constructor
attribute, but try to use duck-typing instead.
>>> new Date().constructor == Date true >>> .1.constructor == Date false >>> .1.constructor == Number true
Gecko (the engine powering Firefox) offers a very handy property on any element __proto__
that returns the prototype. I’ll not use it here to avoid any confusions. It’s the only way I know to get the prototype of a prototype.
Let’s forget this quickly and start doing something interesting, something that looks like inheritance but relies on prototypal chaining.
>>> function Shape(name) { this.name = name }; >>> Shape.prototype.toString = function() { ... return "<Shape \""+this.name+"\">" ... }; >>> new Shape("blob"); <Shape "blob"> >>> function Circle(radius) { ... Shape.call(this, "circle"); ... this.radius = radius ... } >>> Circle.prototype = new Shape(); >>> Circle.prototype.constructor = Circle; >>> Circle.prototype.area = function() { ... return this.radius * this.radius * Math.PI ... }; >>> var circle = new Circle(1); >>> circle <Shape "circle"> >>> circle.area() 3.14159
The tricky thing here is those two lines:
>>> Circle.prototype = new Shape(); >>> Circle.prototype.constructor = Circle;
The first one says that the prototype of a Circle
is a Shape
. It’s an instance of a Shape
. And the constructor
is set back to enable testing instances as you’ve seen previously. As the graph shows, an instance of a Circle
has two elements name
and radius
, the function area
belongs to its prototype. This prototype is a Shape
so it has a name
too, its value is undefined here.
This is one way of doing prototypal inheritance, another way would have be by doing a copy of the methods of one object’s prototype to the other one. And because everything is an object, this is not a real copy, just another reference pointing to the same values.
In the previous example, calling the function toString
will have to walk up two levels of prototypes in order to find it. Let’s us an alternative way.
>>> for(var key in Shape.prototype) { ... if(Shape.prototype.hasOwnProperty(key) && ... key !== "constructor") ... Circle.prototype[key] = Shape.prototype[key] ... }
By doing so, let’s look at the result graph.
The graph is more flat that before, it keeps everything at the same level, which is good because walking up prototypes might take time but you cannot monkeypatch a whole chain of prototype by touching only one of them. It’s a tradeoff.
To conclude, let’s compare two ways of doing (apparently) the same thing.
>>> function Foo() { ... this.toString = function() { ... return "Foo"; ... } ... } >>> new Foo(); "Foo" >>> function Bar() {} >>> Bar.prototype.toString = function() { ... return "Bar" ... }; >>> new Bar(); "Bar"
Calling new Foo().toString()
will be very direct, the function belongs to the instance. So doing new Bar().toString()
will have to look into the prototype to find the function to call. Foo
is faster but will take more memory because toString
will be duplicated in every instances and Bar
will be a little slower but creating many instances won’t duplicate toString because it lives into the prototype and not the instance. And you can modify (monkeypatch) the toString method of a Bar for all the instances at once.
I’ve made a little test that creates a huge chain of prototypes and count how many time is spent walking up (or down) it. It takes some time but only when the chain is insanely long. So don’t (premature) optimize on that. Run the test.