0

I know that classical pattern for inheritance with prototype is based to set the object prototype of the constructor function. However my willing is to have the possibility to call the parent constructor from the derived class constructor with some arguments that are not available before the call.

This is well accomplished in Java/Python/PHP with super(...) or Parent.__init__(...) method.

However in plain Javascript (not coffescript or similar) there is no way to do that ( parent.apply(this,arguments) does but not it does not setup the prototype).

After some readings I've got this solution, adding the "inherits" method to the prototype of Function. This simply add the definition of super to the derived function in order to initialize the prototype according some parameters.

Function.prototype.inherits=function(parent)
{
    var ctor=this;
    var p=Object.create(parent);
    ctor.super=function()
    {
        parent.apply(p,arguments);
    }
    ctor.prototype=p;
}


//testing

function A(x)
{
    this.x=x;
}

function B(y)
{
    B.super(y*2);//Here "super" is available and I can pass parameters. 
    this.y=y;
}
B.inherits(A);//here I define the inheritance


a=new A(3);
b=new B(5);


console.log(a);//returns { x: 3 }
console.log(b);//returns { y: 5 }
console.log(b.x);//returns 10


console.log(a instanceof A);//returns true
console.log(b instanceof B);//returns true

In this way I've got the behaviour that I expected. My question is: what are the drawbacks of this solution? Are there more efficent solutions to the same problem ? Is this solution cross-browser?

PS: I've invented it by myself :)

EDIT: in order to avoid collisions with other libraries I could define a standalone function like this that accomplish the same target.

function inherits(klass,parent)
{
    var p=Object.create(parent);
    klass.super=function()
    {
        parent.apply(p,arguments);
    }
    klass.prototype=p;
}

And in the testing after definition of B simply call

inherits(B,A);

EDIT 2: After Moolamaduck consideration I have rewritten the code in order to solve the problem of shared prototype. The result is pretty simple to use and elegant (IMHO).
https://stackoverflow.com/a/33270707/76081

Community
  • 1
  • 1
alexroat
  • 1,687
  • 3
  • 23
  • 34
  • One drawback is that you're modifying a built-in object, this is generally considered (for good reasons) to be bad practice. Modifying the prototypes of Function/Array/Object etc. is a bad (or at least fragile) idea. In terms of efficiency, if your object creation is your bottleneck you are almost certainly doing it wrong. Cross browser? Unless you need to support old IE you're fine. – Jared Smith Oct 19 '15 at 23:59
  • Efficiency in terms of what metric? Run time? Code complexity/maintainability? – Rick Viscomi Oct 20 '15 at 00:00
  • Also, is there some huge objection you have against transpilers? Coffeescript, babel, and traceur all handle `super` just fine. Or is this more for an educational purpose? – Jared Smith Oct 20 '15 at 00:06
  • Yep, efficency in therms of speed and memory. – alexroat Oct 20 '15 at 00:08
  • I simply would like to find a way to get the result using plain javascript without engage coffescript and similar. This in order to reduce the dependencies. Call it useless optimization for educational purposes. – alexroat Oct 20 '15 at 00:10
  • If you're that concerned about speed and memory then copy paste the shared code and save yourself the scope resolution lookups of your inheritance chain. Or write your code in C++ and compile it with emscripten. Or do any other number of unmaintainable things. But these kind of micro-optimizations you're talking about are a complete waste of time unless your doing something like a particle system where you have 1000s to millions of objects being created constantly. In which case you should be using WebGL. – Jared Smith Oct 20 '15 at 00:13
  • As far as it goes for educational purposes, you can jsperf it but it can and will change with the seasons. JIT compilers find some things easier to optimize at first but get better over time. Some things that used to be done for performance (e.g. joining an array of strings instead of using `+`) are now slower than the 'optimized' version of yesteryear. – Jared Smith Oct 20 '15 at 00:17
  • Sure ... My aim is to have some easy to use way to implement inherits method with super constructor in plain javascript. I presented my solution here to have just a feedback in order to check possible weak points. – alexroat Oct 20 '15 at 00:17
  • 1
    It is trivially correct with no important flaws other than incompleteness. How well does it handle subclassing `Array` for instance? How bout `Date`? `RegExp`? If all you want is to subclass your own user-defined constructor functions then I think you will find your version easy to maintain and plenty performant with the caveats discussed above. – Jared Smith Oct 20 '15 at 00:22
  • Thanks Jared. This is a good point. i will investigate its usability to them. – alexroat Oct 20 '15 at 00:24

4 Answers4

1

One drawback could be that extending prototype of built-in objects like Function or Array has the possibility to clash with third party scripts that run on the page. If two scripts try to overwrite the same prototype, you can get unexpected results.

In terms of browser compatibility, it appears that the weakest link is Object.create, which is not supported in IE 8 and older. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Browser_compatibility

Rick Viscomi
  • 8,180
  • 4
  • 35
  • 50
  • `Object.create` is trivial to polyfill. – Jared Smith Oct 20 '15 at 00:03
  • That doesn't mean it is. I was referring to the OP's code in a vacuum. – Rick Viscomi Oct 20 '15 at 00:04
  • Thank you. Who cares anymore about IE8 :) About the clash of third party script see the edit – alexroat Oct 20 '15 at 00:05
  • I personally don't, but considering IE 8 has greater market share than IE 9 and 10, it's still something to consider. Source: http://gs.statcounter.com/#browser_version_partially_combined-ww-monthly-201409-201509-bar – Rick Viscomi Oct 20 '15 at 00:08
  • You're right ... probably there is a way to modify it and make it work with IE8... Targeting the mobile and the recent browsers I think IE8 could be considered outdated for the modern webapps. – alexroat Oct 20 '15 at 00:12
1

It seems like you're looking for a general pattern in which to recreate the classical Object Orientated paradigm in JavaScript (including use of super).

A few years ago John Resig (the creator of jQuery) suggested a set of functions that allows you to mimic this in Javascript.

http://ejohn.org/blog/simple-javascript-inheritance/

However, for a couple of reasons (including use of the arguments.callee property) this in now considered deprecated, but can still act as a good base.

There are a number of improvements that have been suggested which replace the arguments.callee property, including:

Is John Resig's Javascript inheritance snippet deprecated?

https://codereview.stackexchange.com/questions/30018/improving-on-john-resigs-simple-javascript-inheritance-avoiding-new

If you're looking for a reliable way to recreate classical Object Orientation in JavaScript, I would research further into improvements on John's original code (the guy knows what he's talking about and it's a good base to build on).

Community
  • 1
  • 1
Alvin Pascoe
  • 1,189
  • 7
  • 5
1

Here's a minimal example which achieves what you want (calling a parent constructor from within the constructor of a derived class):

var Shape = function(sides) {
  this.sides = sides;
};

var Square = function(size) {
  /* Just call the parent constructor function with `this` as context. */
  Shape.call(this, 4);
  this.size = size;
};

/* Set up the prototype chain. Use a shim for `Object.create` if you want. */
Square.prototype = Object.create(Shape.prototype);

That's all there is to it: call the parent constructor with the object being constructed as context, and set up the prototype chain.

There is one serious flaw in the code you posted. Namely, you are calling the parent constructor with the prototype of the derived class as context, rather than the object being constructed. This means that members which the parent constructor initializes will be updated on the prototype of all instances of the derived class, and so all instances will be updated. This is not what you want.

To illustrate the problem:

Function.prototype.inherits=function(parent)
{
    var ctor=this;
    var p=Object.create(parent);
    ctor.super=function()
    {
        parent.apply(p,arguments);
    }
    ctor.prototype=p;
}


function A(x)
{
    this.x=x;
}

function B(y)
{
    B.super(y*2);
    this.y=y;
}

B.inherits(A);

var b1 = new B(1);
var b2 = new B(2);
alert(b1.x); // displays "4" instead of "2"!
mooiamaduck
  • 2,066
  • 12
  • 13
  • Thanks! I've got the problem and fixed the code... see the last answer: http://stackoverflow.com/a/33270707/76081 – alexroat Oct 21 '15 at 23:05
0

After Moolamaduck consideration I have rewritten the code in order to solve the problem of shared prototype. The result is pretty simple to use and elegant (IMHO).

Here is it, with testing:

function inherits(ctor,parent)
{
    ctor.prototype=Object.create(parent.prototype);
    ctor._super=function()
    {
        parent.apply(this,arguments);
    }
}


//testing

function A(x)
{
    this.x=x;
};

function B(y)
{
    B._super.call(this,y*2);//Here "_super" is available (after calling inherits) and I can pass parameters to the parent constructor.
    this.y=y;
};
inherits(B,A);// Here we call inherits, after this parent will be available in the B constructor


a=new A(3);
b=new B(5);


console.log(a);//returns A {x: 3}
console.log(b);//returns B {x: 10, y: 5}
console.log(b.x);//returns 2*5=10

console.log(a instanceof A);//returns true
console.log(b instanceof B);//returns true

//post instantiation method definition and inheritance
A.prototype.test=function(){console.log(this.x+1);};
a.test();//returns 3+1=4
b.test();//returns 2*5+1=11



var b1 = new B(1);
var b2 = new B(2);


console.log(b1.x);//returns 2
console.log(b2.x);//returns 4
alexroat
  • 1,687
  • 3
  • 23
  • 34