JavaScript inheritance: when constructor has arguments

15,416

Solution 1

Well, if you want to make B.prototype an object that inherits from A.prototype, without executing the A constructor, to avoid all possible side-effects, you could use a dummy constructor to do it, for example:

function tmp() {}
tmp.prototype = A.prototype;
B.prototype = new tmp();
B.prototype.constructor = B;

You could create a function to encapsulate the logic of the creation of this new object, e.g.:

function inherit(o) {
  function F() {}; // Dummy constructor
  F.prototype = o; 
  return new F(); 
}

//...
B.prototype = inherit(A.prototype);
B.prototype.constructor = B;

If you target modern browsers, you could use the ECMAScript 5 Object.create method for the same purpose, e.g.:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
//..

Solution 2

Although this is an old topic, I thought I'd respond anyway. Two ways to do it:

Although the Pseudo Classical way is the most popular, it has its down sides since it needs to call the parent constructor once in the child constructor and once while inheriting the prototype. Besides, the child's prototype will contain all the properties of the parent constructor which will anyway get overwritten when the child constructor is called. My personal choice is Prototypal Inheritance.

1. Pseudo Classical Inheritance:

function A(x, y) {} 
A.prototype.run = function () {};
function B(x, y) {
    A.call(this,x,y);
}
B.prototype = new A();
B.prototype.constructor = B;

2. Prototypal Inheritance:

function A(x, y) {}
A.prototype.run = function () {};
function B(x, y) {
    A.call(this,x,y);
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Solution 3

The problem is that you can't easily create a prototype object for B since invoking the constructor of A can't be done. This is due to the parameters for the constructor being unknown before new B is executed. You need a dummy constructor function to construct a prototype for B that links to A's prototype.

B.prototype = (function(parent){
    function protoCreator(){};
    protoCreator.prototype = parent.prototype;
    // Construct an object linking to A.prototype without calling constructor of A
    return new protoCreator();
})(A);

Once you've got the prototype object for B set up, you need to ensure to call the constructor of A in the constructor of B.

function B(x, y) {
    // Replace arguments by an array with A's arguments in case A and B differ in parameters
    A.apply(this, arguments);
}

You should now be able to instantiate B by calling new B(x, y).

For a complete same including parameter validation in A see a jsFiddle.

In your original code you are setting B.prototype.constructor = B. I'm not getting why you are doing this. The constructor property does not influence the inheritance hierarchy for which the prototype property is responsible. If you want to have the named constructor contained in the constructor property you'd need to extend the code from above a little:

// Create child's prototype – Without calling A
B.prototype = (function(parent, child){
    function protoCreator(){
        this.constructor = child.prototype.constructor
    };
    protoCreator.prototype = parent.prototype;
    return new protoCreator();
})(A, B);

Using the first definition of B.prototype you'd get the following results:

var b = new B(4, 6);
b.constructor // A
console.info(b instanceof A); // true
console.info(b instanceof B); // true

With the extended version, you'll get:

var b = new B(4, 6);
b.constructor // B
console.info(b instanceof A); // true
console.info(b instanceof B); // true

The cause for the different output is that instanceof follows up the whole prototype chain of b and tries to find a matching prototype object for A.prototype or B.prototype (in the other call). The b.constructor prototype does refers to the function that was used to define the instances prototype. In case you wonder why it does not point to protoCreator this is because its prototype was overwritten with A.prototype during the creation of B.prototype. The extended definition as show in the updated example fixes this constructor property to point to a more appropriate (because probably more expected) function.

For daily use, I'd recommend to discard the idea of using the constructor property of instances entirely. Instead do use instanceof since its results are more predictable/expected.

Solution 4

Consider this:

function B( x, y ) {
    var b = Object.create( new A( x, y ) );

    // augment b with properties or methods if you want to

    return b;
}

And then

var b = new B( 12, 13 );

Now b inherits from an instance of A, which in turn inherits from A.prototype.

Live demo: http://jsfiddle.net/BfFkU/


Object.create isn't implemented in IE8, but one can easily manually implement it:

if ( !Object.create ) {
    Object.create = function ( o ) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

This can be placed inside a ie8.js file which is loaded only for IE8 and below via conditional comments.

Share:
15,416

Related videos on Youtube

Grace Huang
Author by

Grace Huang

Spoofer

Updated on July 12, 2022

Comments

  • Grace Huang
    Grace Huang almost 2 years

    Using pure JavaScript to do inheritance, this is what I usually do:

    function A() {}
    A.prototype.run = function () {};
    
    function B() {}
    B.prototype = new A;
    B.prototype.constructor = B;
    

    Since there is no arguments to pass into the constructor, new A has nothing to complain about. Now, I haven't figured out a good way to do inheritance if the constructor has arguments to pass. For example,

    function A(x, y) {}
    A.prototype.run = function () {};
    
    function B(x, y) {}
    B.prototype = new A;
    B.prototype.constructor = B;
    

    I could pass some arbitrary values like:

    B.prototype = new A(null, null);
    

    In some cases, I may need to validate x and y in the constructor of A. In some extreme cases, I need throw errors when checking x or y. Then, there is no way for B to inherit from A using new A.

    Any suggestions?

    Thanks!

  • user113716
    user113716 almost 13 years
    I think the side-effects is what OP was hoping for. "In some cases, I may need to validate x and y in the constructor of A."
  • Christian C. Salvadó
    Christian C. Salvadó almost 13 years
    @patrickdw Maybe I misunderstood but I thought he was having problems when those validation throwed errors "In some extreme cases, I need throw errors when checking x or y. Then, there is no way for B to inherit from A using new A.", and I thought that he was looking for a way to avoid executing the A constructor when assigning the B.prototype object...
  • user113716
    user113716 almost 13 years
    Yeah, as I think about it more, I think you're right. Though it would seem that the validation in A would likely need to apply to objects constructed by B, but that would be a side issue. For avoiding the validation issues when assigning the new A to B.prototype, this makes sense.
  • Grace Huang
    Grace Huang almost 13 years
    @CMS Thank you for your answer. I'm wondering why I cant do: function A() {}; A.prototype.run = function () {}; function B() {}; B.prototype = A.prototype; B.prototype.constructor = B;
  • Grace Huang
    Grace Huang almost 13 years
    Thank you for your answer! I think I have the same question for you. . I'm wondering why I cant do this directly without using a mediator: function A() {}; A.prototype.run = function () {}; function B() {}; B.prototype = A.prototype; B.prototype.constructor = B;
  • Christian C. Salvadó
    Christian C. Salvadó almost 13 years
    @GraceShao you're welcome, if you do that A.prototype and B.prototype will refer to the same object, any specific property you may add to B.prototype will be on A.prototype and instances of both constructors will inherit them. Creating an object that inherits from A.prototype solves the problem. Also, you might want to run the A constructor within B, maybe to share the same initialization logic, you can do A.apply(this, arguments); within B if both constructors share the same arguments, otherwise, get the necessary arguments for A and you can use A.call(this, arg1, arg2);
  • Augustus Kling
    Augustus Kling almost 13 years
    If you'd set B.prototype = A.prototype you'd loose the ability to add methods or properties to B instances without them being present in A instances. Your assignment would lead to instances of A and B sharing the same prototype – the prototype is where the properties and methods of instances come from.
  • Grace Huang
    Grace Huang almost 13 years
    The reason why I was using B.prototype.constructor = B is that, after B.prototype = new A, the constructor of B is actually pointing to A. This way, I wouldn't be able to extend the constructor of B. Actually your extended examples tell about this problem too.
  • Šime Vidas
    Šime Vidas almost 13 years
    @Grace Yes, Object.create is ES5. See information about the level of implementation in browsers here: kangax.github.com/es5-compat-table
  • gagarine
    gagarine over 10 years
    You can find more information about object create on developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • Chugaister
    Chugaister over 6 years
    I am new to JS, could you please explain how this can be used and order of constructors called? Ca I use it with constructors?
  • A K
    A K about 6 years
    @Chugaister, I'm not sure I quite understand your question. In both examples above, both functions A and B are constructors. B is a constructor that inherits from A. You can use these constructors to create instances like so: var a = new A(); var b = new B();
  • Nir Lanka
    Nir Lanka about 4 years
    This is very useful. Thanks for the simple and elegant code snippets for comparison.