4

In an object, for nothing more than purely aesthetic reasons, I'm wondering if there's a way to allow access to 'this' members from within sub-objects.

Take this example (which I've mirrored at http://jsfiddle.net/Wzq7W/5/):

function BlahObj() {
    this.users = {};
}
BlahObj.prototype = {
    userGet: function(id) {
        if (this.users[id] !== undefined) {
            return this.users[id];
        }
    },
    userAdd: function(id, name) {
        if (this.users[id] === undefined) {
            this.users[id] = name;
        }
    },
    user: {
        get: function(id) {
            if (this.users[id] !== undefined) {
                return this.users[id];
            }
        }
    }
}

var test = new BlahObj();
test.userAdd(1, 'User A');
test.userAdd(2, 'User B');
test.userAdd(3, 'User C');
test.userGet(2);
test.user.get(1);

The userGet() method will properly return 'User B', but due to scope, user.get() cannot access the base member.

In context, this object will have many more members and methods that go along with it, so camel-casing seems so dirty; being able to separate them out by group (user.get(), queue.foo(), room.data()) seems like a better approach in theory. Is it practical? Is there a way to do what I'm asking, in the way I'm looking to do it, or would I just be better off with camelCase?

Thanks for your thoughts!

10
  • It's not a excellent solution, but in the BlahObj constructor you can do this.user.get = this.user.get.bind(this); to bind it to the correct object. Commented Feb 13, 2012 at 5:00
  • stackoverflow.com/questions/436120/… You might want to check out this question Commented Feb 13, 2012 at 5:09
  • Thanks for the response, Jibi. However, I don't believe this is quite the same, as the pattern used in that question uses private members, whereas mine are public. @Twisol: That does work, but as you mentioned is a little unwieldy to have to do that for each sub-member. Appreciated all the same! Commented Feb 13, 2012 at 5:12
  • btw the if (this.users[id] !== undefined) check is useless as it will return undefined if it's undefined anyway. Commented Feb 13, 2012 at 5:21
  • I would personally prefer to change the design a little, using 'test.users.add()' and 'test.users.get()'. It sounds like you logically have a collection here, with the possibility of exposing other collections as "sub-objects" as well. Commented Feb 13, 2012 at 5:21

3 Answers 3

3

DEMO

Change this:

user: {
    get: function(id) {
        if (this.users[id] !== undefined) {
            return this.users[id];
        }
    }
}

To:

user: function() {
    var that = this;

    return {
        get: function(id) {
            if (that.users[id] !== undefined) {
                return that.users[id];
            }
        }
    };
}

You have to call it like test.user().get(1) instead of test.user.get(1) though

Sign up to request clarification or add additional context in comments.

2 Comments

Usage here also changes to test.user().get(1).
@Twisol: I added that before I even saw your comment. Thanks though
3

The userGet() method will properly return 'User B', but due to scope, user.get() cannot access the base member.

The value of a function's this key word has nothing to do with scope. In a function, this is a local variable whose value is set by the call, not by how the function is declared or initialised.

You can use ES5 bind to change that (and add Function.prototype.bind for those environments that don't have it), or you can use a closure or similar strategy instead of this.

Edit

Consider the camelCase option instead, I think it's much cleaner than creating a sub–level, i.e.

getUser: function() {...},
addUser: function() {...},
...

rather than:

user: {
  get: function(){},
  add: function(){},
  ...
}

It's one character less to type too (e.g. addUser vs user.add)

2 Comments

Would I have to bind() each sub-member individually, as Twisol mentioned in my my question (comment 1) ? In other words, if BlahObj had 5 sub-objects, and each of those had 5 methods, would I have to write out 25 bind() statements?
Yes. But consider camelCase instead, you are creating a level of abstraction because of a naming scheme, not because it's good architecturally.
0

Another friend of mine suggested a loop in the constructor that would bind() similar to what both @RobG and @Twisol had suggested, but without having to specifically write out each line.

There are probably other ways to do that loop so it's not specific to two levels deep, but this might be good enough for now. Are there any implications I should be aware of, specifically performance, or other considerations I may not be taking into account?

function BlahObj() {
    this.users = {};

    for (sub in this) {
        for (key in this[sub]) {
            if (typeof this[sub][key] == 'function') {
                this[sub][key] = this[sub][key].bind(this);
            }
        }
    }
}
BlahObj.prototype = {
    userGet: function(id) {
        if (this.users[id] !== undefined) {
            return this.users[id];
        }
    },
    userAdd: function(id, name) {
        if (this.users[id] === undefined) {
            this.users[id] = name;
        }
    },
    user: {
        get: function(id) {
            if (this.users[id] !== undefined) {
                return this.users[id];
            }
        }
    }
}

var test = new BlahObj();
test.userAdd(1, 'User A');
test.userAdd(2, 'User B');
test.userAdd(3, 'User C');
test.userGet(2);
test.user.get(1);

1 Comment

No further input, this was the solution that worked best for me. Sorry :(

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.