Search code examples
javascriptprototypal-inheritance

Javascript extend from constructor


Lets say I have this:

function Foo(){
   this.name = "fooos";
};

Now later in the script I have a reference to Foo, and what to add properties to Foo when instantiated. I could do something like this:

Foo.prototype.myProp = 3;

foo = new Foo();

expect(foo).to.have.property('name');
expect(foo).to.have.property('myProp');

This works great until I need to attach an object to prototype, like so:

Foo.prototype.myProp = { bar : 3 };

The issue is that now instances will share state on bar (as `bar is just a reference):

foo = new Foo();
foo2 = new Foo();

foo.myProp.bar = 5;
expect(foo2.myProp.bar).to.equal(5);

Which is not desirable. However if the object is added inside the constructor the instances do not share state.

Is there a way around this? I don't have access to the constructor to manipulate it. I need to attach an object to an instance from the prototype.


Solution

  • Properties don't have to be on prototypes, you can just add them directly to instances:

    var f = new Foo();
    f.myProp = { bar: 3 };
    

    I know that. Its not what I want.

    The only way I can think of to do this via the prototype with a property (not a method; @meager has a solution using a method) is an ugly hack where you define the property on the prototype with Object.defineProperty (ES5-only) and then redefine it on first access, like this:

    Object.defineProperty(Foo.prototype, "myProp", {
        get: function() {
            var obj = { bar: 3 };
            Object.defineProperty(this, "myProp", {
                get: function() {
                    return obj;
                },
                set: function(value) {
                    obj = value;
                }
            });
            return obj;
        }
    });
    

    Live Example | Source

    I would not recommend that. :-)


    An alternative would be to throw a facade in front of Foo, like this:

    var OriginalFoo = Foo;
    Foo = function() {
        OriginalFoo.apply(this, arguments);
        this.myProp = { bar: 3 };
    };
    Foo.prototype = Object.create(OriginalFoo.prototype);
    

    That would, of course, only apply to objects created via new Foo after this code executed.