Fergal
Fergal

Reputation: 2474

Javascript prototype on properties

I'd like to have an object with multiple levels of methods and properties. The top level will have properties and methods. Some of these properties will then act as name-spaces for second level methods and properties.

e.g.

//first level methods
base.doStuff();
base.doMore();

//second level methods
base.level2.doStuff();

Doing the first level is straight forward:

function Base(foo) {
    this.foo = foo;
}

Base.prototype.doStuff = function () {
    console.log(this.foo);
}

Base.prototype.doMore = function () {
    console.log(this.foo);
}

base = new Base("bar");
base.doStuff();

Is it possible to get a second level, where in the function expression the "this" keyword points back to the Base constructor?

Upvotes: 0

Views: 142

Answers (3)

Martin Jespersen
Martin Jespersen

Reputation: 26183

This is a really bad idea, but here goes:

function Base() {
    this.name = 'Base';
    this.level2 = new Level2(this);
}

Base.prototype.whatsMyName = function(){
    alert(this.name);
};

function Level2(base) {
    this.name='Level2';
    for(var func in Level2.prototype) {
        this[func] = Level2.prototype[func].bind(base);
     }
}

Level2.prototype.whatsMyName = function(){
    alert(this.name);
};

var b = new Base();

b.whatsMyName(); //Base
b.level2.whatsMyName(); //Also Base

You can see it running here: http://jsfiddle.net/zLFgd/1/

Upvotes: 0

nrabinowitz
nrabinowitz

Reputation: 55678

It's much easier to do this without prototypes:

function Base() {
    var base = this;
    base.level2 = {
        moreStuff: function() {
            // use "base" instead of "this" here
        }
    };
}

This can be combined with either prototypical methods, as in your example, or methods defined directly on base in the constructor. The downside of this is that you are creating the method functions every time you instantiate a new object, so you miss some of the shared-prototype goodness of standard prototypical methods.

You could create a new prototype-based object to be your level2:

function Level2() {}
Level2.prototype.moreStuff = function() {
    // do stuff
}

function Base() {
    this.level2 = new Level2();
}

But the methods of base.level2 won't be bound to base unless you bind them explicitly. Various libraries have bind support (e.g. Underscore's _.bind), or you can do it in plain JS:

function Base() {
    var base = this;
    base.level2 = new Level2();
    base.level2.moreStuff = function() {
        return Level2.prototype.moreStuff.apply(base, arguments);
    }
}

You could further simplify here, but you're always going to have to make new methods bound in one way or another, because JS is never going to assign this in base.level2.moreStuff() to base without explicit binding - so in most cases the first option is the easiest and cleanest.

But really, is it worthwhile just for namespacing? If there's no functional value, it's a lot harder than simply calling your methods level2MoreStuff(), etc.

Upvotes: 1

metadings
metadings

Reputation: 3848

Well,

base.doStuff();

is calling doStuff in context of base. It is the same as

base.doStuff.call(base);

You can call and apply any function, for overriding this:

var base = new Base();

var someFun = function () {
    console.log (this === base); // true
};

someFun.call(base);

Further anonymous example:

var anObj = {
    method0: function () {
        console.log (this === anObj); // true
    }
};

anObj.method1 = function () {
    console.log (this === anObj); // true
};

anObj.method0();
anObj.method1();

So the "second level" points this to level2, not to the "first level" object.

Upvotes: 0

Related Questions