I am concerned about the following code piece:
//subclass extends superclass
Rectangle.prototype = new Shape();
You should be, that's a common pattern, but not a very good one, because then you're using Shape
both to create prototypes and to initialize instances. It should do one or the other. (Consider, for instance, what you'd do if you needed to give Shape
arguments? What args would you use creating the Rectangle.prototype
?)
But you don't want Rectangle.prototype = Shape.prototype
either, because then you can't add things to Rectangle.prototype
(since it's pointing at the same object Shape.prototype
is pointing at, you'd add them to Shape
instances as well).
So what we want is to create a new object that uses Shape.prototype
as its prototype (e.g., that inherits from Shape.prototype
) just like the objects created via new Shape
do, but without calling Shape
to create it; and then use that new object as Rectangle.prototype
.
As of ECMAScript5, we can do that using Object.create
:
Rectangle.prototype = Object.create(Shape.prototype);
Object.create
creates a new object and assigns the first argument you give it as the object's underlying prototype.
Having done that, although it's not strictly necessary, we should also set the constructor
property on our new object so that it refers to Rectangle
:
Rectangle.prototype.constructor = Rectangle;
We do that because that way, the relationship between Rectangle.prototype
and Rectangle
looks the way §13.2 of the spec says it should look. Basically, if you have a constructor function X
, then X.prototype.constructor
should refer to X
. Sometimes people rely on that for cloning operations and such (JavaScript itself does not rely on that — for instance, instanceof
doesn't rely on it).
So we can use ES5's Object.create
to do this. But even today, not all JavaScript engines support Object.create
. So instead, we can do this indirectly, by creating a temporary constructor function, letting it borrow Shape.prototype
as its prototype
property, and then using that temporary constructor to create our new object to use as Rectangle.prototype
:
function Ctor() { }
Ctor.prototype = Shape.prototype; // Borrow the prototype
Rectangle.prototype = new Ctor(); // Create the new object
...and again we should set the constructor
property:
Rectangle.prototype.constructor = Rectangle;
Typically, rather than writing all that out each time, you use a helper like this:
function derive(Parent, Child) {
function Ctor() { this.constructor = Child; }
Ctor.prototype = Parent.prototype;
Child.prototype = new Ctor();
return Child; // For chaining
}
Then use it like this:
derive(Shape, Rectangle);
The end result of all this is that we only call Shape
from within Rectangle
, to initialize instances. We don't call it to create Rectangle.prototype
.
If you're interested in inheritance plumbing in JavaScript, you might be interested in my Lineage
script, which handles the above and a lot more besides. When I say "interested," I don't necessarily mean using it (although you're welcome to), but looking at the unminified source and at this page comparing Lineage
with doing the same thing without a helper could give you an idea how this stuff works.