Making subclass with .prototype and __proto__
Asked Answered
Z

3

7

I've recently been learning javascript by writing some gnome shell extensions, and hence my understanding of Javascript has been shaped by the examples I've observed in the gnome-shell javascript sources. I have a feeling I've been understanding classes wrong and just want some clarification.

I've written a few of my own subclasses, and in each case I've defined them simply by following similar code in the gnome-shell javascript source:

Subclass = function() {
    this._init.apply(this,arguments);
}
Subclass.prototype = {
    __proto__: Superclass.prototype,
    _init: function() {
        Superclass.prototype._init.call(this);
    },
    // add other methods of Subclass here.
}

Up to now I thought this was the standard way of making a class Subclass that was basically Superclass plus extras. I assumed that every object had a _init method.

I've recently tried to apply the same method to make a subclass of a Clutter.Actor (what's important is that it's not a GNOME-shell-defined class), and realised that the above way of subclassing objects is not the standard. For one, not every class has a _init function as I assumed; this is just something that GNOME-shell has done in their javascript classes.

So, my questions are:

  1. Is there any documentation regarding the above method of creating subclasses? All the tutorials I've seen say to set Subclass.prototype = new Superclass() instead of doing the Subclass.prototype = { __proto__:Superclass.prototype, define_prototype_methods_here } method, but my thought is that there must be some method to it if gnome-shell uses it consistently?
  2. If I wanted to stay as close as possible to the above way of defining classes (just so I can retain some code similarity to GNOME-shell for which I am writing extensions), what do I replace Superclass.prototype._init.call(this) with in Subclass._init to make sure that the Subclass.prototype gets all the methods/properties of Superclass (which I then add on to in my definition of Subclass.prototype), if the Superclass doesn't have an _init function (i.e. does it have some equivalent constructor function I call)?

I'm really confused about this all so please forgive me if my question doesn't make much sense; it'll be because of the extent of my misunderstanding & confusion!

EDIT: clarification: - I know the __proto__ is not recommended because it is non-standard, but my code is never going to run in a browser - it's only ever going to run with GNOME javascript (which is basically the Mozilla javascript engine), so I don't need to worry about cross-compatibility.

Zwinglian answered 14/5, 2012 at 8:53 Comment(1)
This is not really an answer, but I highly recommend this site to enhance your JS knowledge: yuiblog.com/crockfordYost
C
11

As already said, don't use __proto__. It's a non-standard property. (It's standardized for JavaScript in browsers now. Still don't use it.) But

Subclass.prototype = new Superclass(); // Don't do this

is not a very good method either. What if Superclass expects parameters?

You have better options.

ES2015 and above

class handles all of this plumbing for you; complete example:

class Superclass {
    constructor(superProperty) {
        this.superProperty = superProperty;
    }
    method() {
        console.log("Superclass's method says: " + this.superProperty);
    }
}
class Subclass extends Superclass {
    constructor(superProperty, subProperty) {
        super(superProperty);
        this.subProperty = subProperty;
    }
    method() {
        super.method(); // Optional, do it if you want super's logic done
        console.log("Subclass's method says: " + this.subProperty);
    }
}

let o = new Subclass("foo", "bar");
console.log("superProperty", o.superProperty);
console.log("subProperty", o.subProperty);
console.log("method():");
o.method();

ES5 and earlier

Let Subclass.prototype inherit from Superclass.prototype only. This can be done for example with ES5's Object.create:

Subclass.prototype = Object.create(Superclass.prototype);
Subclass.prototype.constructor = Subclass;

And then in Subclass, you call Superclass with this referring to the object so it has a chance to initialize:

function Subclass() {
    Superclass.call(this); // Pass along any args needed
}

Full example:

function Superclass(superProperty) {
    this.superProperty = superProperty;
}
Superclass.prototype.method = function() {
    console.log("Superclass's method says: " + this.superProperty);
};
function Subclass(superProperty, subProperty) {
    Superclass.call(this, superProperty);
    this.subProperty = subProperty;
}
Subclass.prototype = Object.create(Superclass.prototype);
Subclass.prototype.constructor = Subclass;

Subclass.prototype.method = function() {
    Superclass.prototype.method.call(this); // Optional, do it if you want super's logic done
    console.log("Subclass's method says: " + this.subProperty);
};

var o = new Subclass("foo", "bar");
console.log("superProperty", o.superProperty);
console.log("subProperty", o.subProperty);
console.log("method():");
o.method();

ES3 and earlier

ES3 and earlier don't have Object.create, but you can easily write a function that sets up the prototype for you in much the same way:

function objectCreate(proto) {
    var ctor = function() { };
    ctor.prototype = proto;
    return new ctor;
}

(Note: You could half-shim Object.create by creating one that takes only one argument, but the multiple-argument version of Object.create cannot be shimmed, so it would be giving other code on the page the wrong idea if it also uses Object.create.)

Then you do the same thing as our ES5 example:

Full example:

function objectCreate(proto) {
    var ctor = function() { };
    ctor.prototype = proto;
    return new ctor;
}

function Superclass(superProperty) {
    this.superProperty = superProperty;
}
Superclass.prototype.method = function() {
    console.log("Superclass's method says: " + this.superProperty);
};
function Subclass(superProperty, subProperty) {
    Superclass.call(this, superProperty);
    this.subProperty = subProperty;
}
Subclass.prototype = objectCreate(Superclass.prototype);
Subclass.prototype.constructor = Subclass;

Subclass.prototype.method = function() {
    Superclass.prototype.method.call(this); // Optional, do it if you want super's logic done
    console.log("Subclass's method says: " + this.subProperty);
};

var o = new Subclass("foo", "bar");
console.log("superProperty", o.superProperty);
console.log("subProperty", o.subProperty);
console.log("method():");
o.method();
Comeaux answered 14/5, 2012 at 9:29 Comment(5)
OK, I understand what you're saying. Does this mean I'm stuck with defining the inherits function just to create a subclass (/doing the fiddling with Tmp to set the prototypes)? (It doesn't bother me that __proto__ is non-standard because my code is only for gnome javascript which is based off the Mozilla javascript engine - it's never intended for browsers).Zwinglian
Lets say it's a cleaner way... in the long run it will pay off I think, plus you can easily transfer this pattern to other environments. So why not learn/use one thing you can apply everywhere? ;) You are not stuck with this, you can use __proto__ but I recommend not to. It's basically the same as using Object.create which is a better way (if the environment supports ES5).Comeaux
Thanks. I will learn the "cleaner way" for the general case. Given that the code I'm writing will not work out of GNOME javascript anyway (which supports __proto__) because of other features of the code, how would I call the superclass constructor if I did want to persist with using __proto__? Do this._init.apply(this,arguments) in Subclass(), and in Subclass.prototype._init do __proto__: Superclass.prototype, _init: function() { Superclass.apply(this,arguments); // rest of initialisation } ?Zwinglian
Yes you can do this. However, there is no need to have an extra _init method. The function itself is already the constructor. Don't make it too complicated ;)Comeaux
Ok, I get it. Thanks! (I only put the _init method in to conform to gnome shell coding style - all gnome shell code is meant to have the same "look" and as I'm writing a gnome shell extension and all of gnome shell has _init methods, I felt like I had to put one in too).Zwinglian
B
1

Although __proto__ has now been standardized as a required extension to JavaScript when used on web browsers, there's no reason to use it when setting up inheritance hierarchies.

Instead, use Object.create (an ES5 function, the key parts of which for our purposes here could be shimmed if you really needed to support antiquated browser).

Here's an example for when

var BaseClass = function() {
};

var SubClass = function() {
    // Important that SubClass give BaseClass a chance to init here
    BaseClass.call(this/*, args, if, necessary, here*/);

    // ...
};

// Make `SubClass`'s `prototype` an object that inherits from
// `BaseClass`'s `prototype`:
SubClass.prototype = Object.create(BaseClass.prototype);

// Fix up the `constructor` property
SubClass.prototype.constructor = SubClass;

That's it.

If BaseClass's constructor requires an argument, you pass it the argument in SubClass:

Here's an example for when

var BaseClass = function(arg) {
    this.prop = arg;
};

var SubClass = function(baseArg) {   // Can accept it, or provide a
    BaseClass.call(this, baseArg);   // hardcoded one here

    // ...
};

SubClass.prototype = Object.create(BaseClass.prototype);
SubClass.prototype.constructor = SubClass;

Of course, as of ES2015 (aka "ES6"), you can just use class (and transpile if necessary).

class BaseClass {
    constructor(arg) {
        this.prop = arg;
    }
}

class SubClass extends BaseClass {
    constructor(baseArg) {
        super(baseArg);
    }
}
Bighead answered 8/10, 2016 at 14:33 Comment(0)
F
0

__proto__ should never be used in your code since it is used by JavaScript Engine implementation to refer back to an objects Class definition prototype. refer: MDN:__proto__

1>

 Subclass.prototype = new Superclass();

is the right way to inherit a super class.

2> if you want to call _init on subclass which will actually execute the Superclass _init method then do not define define it on the SubClass class. When you try to access the _init method on subClass(instance object of SubClass) JS engine tries to look for it on the current obj, if the method is not found it is searched in SubClass proto type chain(i.e. SuperClass)

if you want to call superclass method inside a subclass method then use

Superclass.prototype._init.call(this)

executing the super function in current objects scope. This does the trick of calling the super method within the SubClass method

Fimbriate answered 14/5, 2012 at 9:16 Comment(1)
Note - I'm not running the code in any browser. For 2), I know how to call a parent's method. My question was how to get the effect of calling Superclass.prototype._init.call(this) from within Subclass.prototype's instructor (the effect being that Subclass.prototype gets all the properties & methods of Superclass), when there is no Superclass._init function in Superclass.Zwinglian

© 2022 - 2024 — McMap. All rights reserved.