javascript multilevel inheritance weird result

I'm refreshing my javascript skills and came up with a way that objects can inherit from another. The inheritance should be tree-like.

Parent->Child->Child2

I've extended the Function.prototype (Don't tell me this is a bad idea, this can be changed later)

Function.prototype.extend = function(child)
{
    // save child prototype
    var childPrototype = child.prototype;
    // copy parent to child prototype
    child.prototype = Object.create(this.prototype);
    // merge child prototype
    for (var property in childPrototype) {
        child.prototype[property] = childPrototype[property];
    }
    child.prototype.constructor = child;
    child.prototype.$parent = this.prototype;
    return child;
};

The parent object:

var Parent = (function()
{
    var Parent = function(x, y)
    {
        this.x = x;
        this.y = y;
        console.log('Parent constr', x, y);
    }

    Parent.prototype.move = function(x, y)
    {
        this.x += x;
        this.y += y;
        console.log('Parent moved.');
    };

    return Parent;

}());

First child:

var Child = Parent.extend(function()
{
    var Child = function(x, y)
    {
        this.$parent.constructor(x, y);
        console.log('Child constr');
    }

    Child.prototype.print = function()
    {
        console.log('Child print', this.x, this.y);
    };

    // override
    Child.prototype.move = function(x, y)
    {
        this.$parent.move(x, y);
        console.log('Child moved.');
    };

    return Child;

}());

Second child:

var Child2 = Child.extend(function()
{
    var Child2 = function(x, y)
    {
        this.$parent.constructor(x, y);
        console.log('Child2 constr');
    }

    // override
    Child2.prototype.move = function(x, y)
    {
        this.$parent.move(x, y); // call parent move
        console.log('Child2 moved.');
    };

    return Child2;

}());

So far, so good. I'm able to call the parents constructor and methods and even override methods.

var child = new Child2(1, 1);
child.move(1, 1);
child.print();

I get following output which is correct:

Parent constr 1 1
Child constr
Child2 constr
Parent moved.
Child moved.
Child2 moved.
Child print 2 2

But if I comment out the override from second child I get following output:

Parent constr 1 1
Child constr
Child2 constr
Parent moved.
Child moved.
Child moved. -> WHY??
Child print 2 2

I don't understand why Child moved. is outputted twice. The result is correct but something weird is going on.

EDIT:

Finally after researching and diving more into the problem I came with a nice solution:

// call method from parent object
Object.getPrototypeOf(Child2.prototype).move.apply(this, arguments);

I made another extension to Function:

Function.prototype.getParent = function()
{
    return Object.getPrototypeOf(this.prototype);
};

And then for example the Child2 move method:

Child2.prototype.move = function(x, y)
{        
    Child2.getParent().move.call(this, x, y);
};

So I don't need $parent anymore and I get the desired result.

Another solution would be to call the parent prototype directly:

Child2.prototype.move = function(x, y)
{        
    Child.prototype.move.call(this, x, y);
};

Answers:

Answer

You should be using apply to invoke your functions in the correct scope, also using this to access the parent is not safe. This is actually an excellent problem for learning about JS debugging, you can see everything happening using break points.

looking at your first Child :

// override
Child.prototype.move = function(x, y)
{
    this.$parent.move(x, y);
    console.log('Child moved.');
};

When this code is reached by calling child.move(1,1), this points to child, and what does child.$parent.move point to? why, the implementation in the first Child of course!

This second time around, this points to the prototype of the Parent, so now we're fine.

So that's why this is happening, what can you do about it? Well that's up to you, I mentioned apply because that was my default thought, so you would use this.$parent.move.apply(this, arguments);, but then we really can't get access to higher parents, so the only other option I can think of is binding (hitching, for any dojo people reading) every function to it's respective prototype, thereby guaranteeing its this value always points to the same thing, and therefore it can always access the $parent reliably.

Of course, this would mean no access to the actual object instance, so it's only really useful for pure utility functions, but this is actually not new. If you had overridden print (which accesses the object instance's x and y members) and tried to invoke the parent, that would have broken as well.

Answer

Your extend method attaches the inherited properties and methods to the new object's prototype. It also creates a $parent object which is equal to the prototype of the parent object. Note that this results in duplication. The second child has three move methods:

child.protoype.move
child.$parent.move
child.$parent.$parent.move

Even if you comment out the override there are still three move methods (although two are references to the same function).

Therefore, when you run child.move you get:

child.prototype.move which invokes child.$parent.move which invokes child.$parent.$parent.move

The fact that child.prototype.move and child.$parent.move are both references to the same function does not prevent them from being executed twice.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.