Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 408
corazza Avatar asked Jun 17 '12 15:06

corazza


People also ask

Is prototype an inheritance?

The Prototypal Inheritance is a feature in javascript used to add methods and properties in objects. It is a method by which an object can inherit the properties and methods of another object. Traditionally, in order to get and set the [[Prototype]] of an object, we use Object. getPrototypeOf and Object.

Does all objects have prototype?

Every object in JavaScript has a built-in property, which is called its prototype. The prototype is itself an object, so the prototype will have its own prototype, making what's called a prototype chain. The chain ends when we reach a prototype that has null for its own prototype.

Why is prototype inherited?

Prototypical inheritance allows us to reuse the properties or methods from one JavaScript object to another through a reference pointer function. All JavaScript objects inherit properties and methods from a prototype: Date objects inherit from Date.

Does function inherit from object in JavaScript?

prototype is last in the prototype chain and it doesn't inherit from anything. The Object constructor is the one that inherits from Function.


1 Answers

Seems like Bla2 didn't inherit it from the Bla 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

like image 141
T.J. Crowder Avatar answered Sep 27 '22 17:09

T.J. Crowder