Objects don't inherit prototyped functions
I have one constructor function, which acts as a superclass:
Bla = function(a){this.a = a;}
I prototype it to include a simple method:
Bla.prototype.f = function(){console.log("f");
And now new Bla(1).f();
will log "f" in the console. But, lets say that I need a subclass that inherits from Bla:
Bla2 = function(a)
{
this.base = Bla;
this.base();
}
x = new Bla2(5);
Now, as expected, x.a
gives me 5
. But, x.f
is undefined
! Seems like Bla2
didn't inherit it from the Bla
class! Why is this happening and how do I correct it?
Seems like
Bla2
didn't inherit it from theBla
class!
Right. You haven't done anything to hook up inheritance there, you've just created a member of Bla2
called base
which is a Bla
instance. base
is not a special identifier in JavaScript.
The typical way to set up inheritance in JavaScript looks like this:
// The base constructor function
function Base(x) {
// Base per-instance init
this.x = x;
}
// An example Base function
Base.prototype.foo = function() {
console.log("I'm Base#foo, x = " + this.x);
};
// The Derived constructor function
function Derived(x, y) {
// Normally you need to call `Base` as it may have per-instance
// initialization it needs to do. You need to do it such that
// within the call, `this` refers to the current `this`, so you
// use `Function#call` or `Function#apply` as appropriate.
Base.call(this, x);
// Derived per-instance init
this.y = y;
}
// Make the Derived.prototype be a new object backed up by the
// Base.prototype.
Derived.prototype = Object.create(Base.prototype);
// Fix up the 'constructor' property
Derived.prototype.constructor = Derived;
// Add any Derived functions
Derived.prototype.bar = function() {
console.log("I'm Derived#bar, x = " + this.x + ", y = " + this.y);
};
...where Object.create
is from ES5, but it's one of the things that can easily be mostly shimmed. (Or you can use a function that only does the bare minimum without trying to do all of Object.create
; see below.) And then you use it:
var d = new Derived(4, 2);
d.foo(); // "I'm Base#foo, x = 4"
d.bar(); // "I'm Derived#bar, x = 4, y = 2"
Live example | source
In older code you sometimes see the Derived.prototype
set up like this instead:
Derived.prototype = new Base();
...but there's a problem with doing it that way: Base
may do per-instance initialization which isn't appropriate for the entirety of Derived
to inherit. It may even require arguments (as our Base
does; what would we pass for x
?). By instead making Derived.prototype
just be a new object backed by the Base.prototype
, we get the correct stuff. Then we call Base
from within Derived
to get per-instance init.
The above is very basic and as you can see involves a number of steps. It also does little or nothing to make "supercalls" easy and highly-maintainable. That's why you see so many "inheritance" scripts out there, like Prototype's Class
, Dean Edwards' Base2, or (cough) my own Lineage
.
If you can't rely on having ES5 features in your environment, and don't want to include a shim that does the basics of Object.create
, you can just use this function in its place:
function simpleCreate(proto) {
function Ctor() {
}
ctor.prototype = proto;
return new Ctor();
}
Then instead of
Derived.prototype = Object.create(Base.prototype);
you'd do:
Derived.prototype = simpleCreate(Base.prototype);
Of course, you can do more to automate hooking things up — which is all Lineage
basically does.
...and finally: Above I've used anonymous functions for simplicity, e.g.:
Base.prototype.foo = function() {
// ...
};
...but I don't do that in my real code, because I like to help my tools help me. So I tend to use the module pattern around each "class" (constructor function and associated prototype) and use function declarations (since I do work for the web, and IE7 and IE8 still have problems with named function expressions. So if I weren't using Lineage
, I'd do the above like this:
// Base
(function(target) {
// Base constructor
target.Base = Base;
function Base(x) {
// Base per-instance init
this.x = x;
}
// An example Base function
Base.prototype.foo = Base$foo;
function Base$foo() {
console.log("I'm Base#foo, x = " + this.x);
}
})(window);
// Derived
(function(target, base) {
// The Derived constructor function
target.Derived = Derived;
function Derived(x, y) {
// Init base
base.call(this, x);
// Derived per-instance init
this.y = y;
}
// Make the Derived.prototype be a new object backed up by the
// Base.prototype.
Derived.prototype = Object.create(base.prototype);
// Fix up the 'constructor' property
Derived.prototype.constructor = Derived;
// Add any Derived functions
Derived.prototype.bar = Derived$bar;
function Derived$bar() {
console.log("I'm Derived#bar, x = " + this.x + ", y = " + this.y);
}
})(window, Base);
...or something like that. Live copy | source