Dale Woods
Dale Woods

Reputation: 849

Accessing parent prototype method via child object like a normal child method

It seems like I don't understand prototypes correctly once again.

If you call a method on an object, and it doesn't exist - does it not check the prototype for the method can run it?

like

Array.prototype.myArrayMagic = function () { ... }

Then on any array you can call arr.myArrayMagic() ?

That was my understanding, but I am now running into issue where calling a prototype method wont work unless I explicitly call it through .prototype

Also: I am restricted to ES5 code

The base module setup

function Wall() {}

Wall.prototype = {

    shouldShowWall: function() {
        return true;
    },

    show: function () {
        if (!this.shouldShowWall()) {
            return;
        }

        // Do other stuff here
    }
}

module.exports = Wall;

The child object

function OtherWall() {
    this.item = 'my_tracking_id',
    this.minutes = 30
    // ...
}

OtherWall.prototype = {
    constructor: Object.create(Wall.prototype),

    /**
     * Override wall.prototype.shouldShowWall method for deciding if the wall should be shown at this time
     */
    shouldShowWall: function() {
        // return true or flase here
    }.bind(this)
}

module.exports = OtherWall;

So following other answers and trying to understand as best as possible, this should be set up in a way where I can call

new OtherWall().show();

but it doesn't work, it only works if I call

new OtherWall().prototype.show();

Is this normal, or have I set it up wrong?

What do I need to change to get it to work with OtherWall().show();

Upvotes: 3

Views: 559

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074148

If you call a method on an object, and it doesn't exist - does it not check the prototype for the method can run it?

Yes, it does (like with any property), but this code is incorrect:

OtherWall.prototype = {
    constructor: Object.create(Wall.prototype)
    // ...

That won't make Wall.prototype the prototype of OtherWall.prototype. To do that, do this:

OtherWall.prototype = Object.create(Wall.prototype);

Then add properties on OtherWall.prototype, starting with constructor:

// But keep reading, I wouldn't do it quite like this
OtherWall.prototype.constructor = OtherWall;

...then shouldShowWall, etc.

My answer here may also be useful, it shows creating a "subclass" using ES5 syntax (contrasting it with ES2015's class syntax).

Another couple of notes:

1. This looks like it's probably not what you intend:

/**
 * Override wall.prototype.shouldShowWall method for deciding if the wall should be shown at this time
 */
shouldShowWall: function() {
    // return true or flase here
}.bind(this)

The this in scope there isn't an instance of OtherWall, it's whatever this is outside that object literal. If you want to bind shouldShowWall to the instance, you'll need to do that in the constructor.

2. I'd suggest making methods non-enumerable, like ES2015's class syntax does, and the same for the constructor property like JavaScript always has. When you create a property via simple assignment, the property is enumerable.

3. I think you have a typo in OtherWall, you've used a , at the end of the first statement instead of a ;. Technically, it works, because of the comma operator, but I don't think you were using the comma operator on purpose. :-)

Here's an example applying all of the above; Object.defineProperty is an ES5 method (I'm out of practice writing ES5 code, but I think I've avoided all ES2015+ stuff here):

function assignNonEnumerable(target, source) {
    Object.keys(source).forEach(function (key) {
        Object.defineProperty(target, key, {
            value: source[key],
            configurable: true,
            writable: true,
            enumerable: false, // This is the default, but showing it here for emphasis
        });
    });
}

function Wall() {
}

assignNonEnumerable(Wall.prototype, {
    shouldShowWall: function () {
        return true;
    },

    show: function () {
        console.log("show called");
        if (!this.shouldShowWall()) {
            return;
        }

        // Do other stuff here
    },
});

function OtherWall() {
    this.item = "my_tracking_id";
    this.minutes = 30;
    // If you want to bind it to the instance:
    this.shouldShowWall = this.shouldShowWall.bind(this);
}

OtherWall.prototype = Object.create(Wall.prototype);
assignNonEnumerable(OtherWall.prototype, {
    constructor: OtherWall,
    shouldShowWall: function () {
        // You can't bind the instance here, see the constructor above
        console.log("shouldShowWall called");
        return Math.random() < 0.5;
    },
});

new OtherWall().show();

Upvotes: 5

Related Questions