Search code examples
javascriptoopinheritancesubclassing

Issue when trying to inherit all classes referenced by name in a for loop


Well the title is a mouthful but I couldn't come up with a better one, ideas welcome.
Anyhow, I have a javascript object containing classes as properties. I want to create another object which is in every aspect equal to the first one by subclassing it. I'm gonna try to sum it up:

var L1 = {};

L1.Foo = function() {/*...*/};
L1.Bar = function() {/*...*/};
//...
L1.Baz = function() {/*...*/};

var L2 = {};

L2.Foo = function() { L1.Foo.call(this); /*possibily some other code here*/ };
L2.Foo.prototype = Object.create(L1.Foo.prototype);
L2.Foo.prototype.constructor = L2.Foo;

L2.Bar = function() { L1.Bar.call(this); /*possibily some other code here*/ };
L2.Bar.prototype = Object.create(L1.Bar.prototype);
L2.Bar.prototype.constructor = L2.Bar;

//...

L2.Baz = function() { L1.Baz.call(this); /*possibily some other code here*/ };
L2.Baz.prototype = Object.create(L1.Baz.prototype);
L2.Baz.prototype.constructor = L2.Baz;

var foo = new L2.Foo();
console.log(foo); //L2.Foo
var bar = new L2.Bar();
console.log(bar); //L2.Bar
var baz = new L2.Baz();
console.log(baz); //L2.Baz

First, working version.
I told myself: "huh, looks like there a pattern here" so I went and modified my code as follows:

//first 10 lines unaltered

for(prop in L1) {
    L2[prop] = function() { L1[prop].call(this); /*Call super method by default,
    unless overriden below*/ };
    L2[prop].prototype = Object.create(L1[prop].prototype);
    L2[prop].prototype.constructor = L2[prop];
}
//Here I decide that I want to override only the constructor
//for Foo, so naturally:
L2.Foo.prototype.constructor = function() {
    L1.Foo.call(this);
    this.someOtherProperty = "foo";
};

var foo = new L2.Foo();
console.log(foo); //L2.(anonymous function)?
console.log(foo.someOtherProperty); //undefined?
var bar = new L2.Bar();
console.log(bar); //L2.(anonymous function)?
var baz = new L2.Baz();
console.log(baz); //L2.(anonymous function)?

Second, not-so-working version.
What I am getting wrong?


Solution

  • "huh, looks like there a pattern here" so I went and modified my code as follows:

    for(prop in L1) {
        L2[prop] = function() { L1[prop].call(this);
    

    You've hit the common closure in a loop problem - all your L2 functions are actually calling L1.Baz on their new instance as prop will have the value "Baz". See the linked question for how to fix this.

    Also, notice that none of your constructors does pass its arguments to the super call, which might bite you as well.


    Here I decide that I want to override only the constructor for Foo, so naturally:

    L2.Foo.prototype.constructor = function() {
        L1.Foo.call(this);
        this.someOtherProperty = "foo";
    };
    

    What I am getting wrong?

    Overwriting the .constructor property on a prototype object does nothing. Your code is still invoking new L2.Foo, not new L2.Foo.prototype.constructor. You might want to have a look at how the new keyword works.

    Instead, you really need to replace L2.Foo. This can be done with this pattern:

    L2.Foo = (function (original) {
        function Foo() {
            original.apply(this, arguments);   // apply old L2.Foo constructor
            this.someOtherProperty = "foo";    // set property
        }
        Foo.prototype = original.prototype; // reset prototype
        Foo.prototype.constructor = Foo; // fix constructor property
        return Foo;
    })(L2.Foo);
    

    (or you just put your standard pattern from the first version). If this does get too repetitive, you might also do the .prototype and .constructor setup programmatically:

    // whole code
    var L2 = {
        Foo: function() {
            L1.Foo.call(this);
            this.someOtherProperty = "foo";
        }
        // … other overwritten constructors
    };
    for (var prop in L1) {
        if (!L2[prop]) // unless overridden above, create default that only…
            (function(parent) {
                L2[prop] = function() {
                    parent.apply(this, arguments); // calls super
                };
            }(L1[prop]));
        L2[prop].prototype = Object.create(L1[prop].prototype);
        L2[prop].prototype.constructor = L2[prop];
    }