3

I've been reading for hours and can't seem to find an answer that suits my needs. I don't want to change up the structure at this point, due to the size of the code. I am trying to find a solution that works from within the structure I've already got in place, if at all possible.

First, here is a very simplified mock-up of my object literal structure:

NS = 
{
    x: undefined,

    button: undefined,

    fn: 
    {
        root: undefined,

        doSomething: function () 
        {
            var root = this.root,
                x = root.x;       // exception occurs here

            // do something with x
        }
    },

    init: function () 
    {
        var self = this,
            x = self.x,
            button = self.button,
            fn = self.fn,
            fn.root = self;

        x = $("#x");
        button = $("#button");    

        button.on("click", fn.doSomething);
    }
};

I know it looks like the declarations under init() aren't really needed, but the namespaces can get rather long, so I like to shorten them like that. This has worked great for me in almost every scenario until I hit this snag. I know I could fully qualify everything and it should work, but I really don't want to do that due to the aforementioned long namespaces.

My issue is that my root property x doesn't keep its value after it was set in the init() function when it's being accessed from within another property's function. You can console.log(this.x) from within the init() function and it's there. However, when you click the button and the onClick function tries to declare x = root.x it throws:

Uncaught TypeError: Cannot read property 'x' of undefined


UPDATE:

Adding console.log() shows that fn.root.x is undefined even before the handler is called:

init: function () 
{
    var self = this,
        x = self.x,
        button = self.button,
        fn = self.fn,
        fn.root = self;

    x = $("#x");

    console.log(x); // this shows the object
    console.log(fn.root.x); // this throws the undefined exception

    button = $("#button");    
    button.on("click", fn.doSomething);
}
Code Maverick
  • 20,171
  • 12
  • 62
  • 114

2 Answers2

3

When doSomething is called as an event handler, this will be the event target inside the function. So this.root will be undefined, and undefined doesn't have any properties, so root.x raises an error.

One solution is to fix the value of this with $.proxy:

button.on("click", $.proxy(fn.doSomething, self));
bfavaretto
  • 71,580
  • 16
  • 111
  • 150
  • Very interesting ... i've not looked at $.proxy before. Let me try that. Do you know if this creates any more overhead than what I'm already trying to do? – Code Maverick Apr 23 '13 at 02:59
  • It will create a new function, using Function.prototype.bind if available. But I don't think that will have any performance impact. – bfavaretto Apr 23 '13 at 03:02
  • Hmm, it didn't end up working. Here's the weird thing. If you `console.log(x)` right after `x = $("#x")`, it's there. Then right after that if you `console.log(fn.root.x)` it's already undefined. – Code Maverick Apr 23 '13 at 03:09
  • That's a different issue: x is a local variable where you're assigning to it; to assign to NS.x you have to use NS.x = ... (there are no pointers in js) – bfavaretto Apr 23 '13 at 03:16
  • It's a little tricky in js, but in short: (1) there are no pointers; (2) you always pass/assign by value; (3) when the value you reference is an object, it's actually a reference to that object, and you can share properties (so `var x = {'foo': true}; var y = x; y.foo = false; y.foo === x.foo /* true */; y === x /* true */`) - but if `y = {}`, then `x` will remain untouched. See http://stackoverflow.com/questions/509579/how-does-variable-assignment-work-in-javascript and http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language. – bfavaretto Apr 23 '13 at 03:38
  • Thanks a lot ... that really helps solidify things – Code Maverick Apr 23 '13 at 03:49
0

Actually, JS objects and arrays are passed by reference. In your example, if x is an object and not undefined:

NS = {
    x: {},
    button: undefined,
    // etc
};

Then in your init method you can do something like this and it will work:

init: function(){
    var self = this,
        x = self.x;

    x.foo = 'foo!';
    console.log(self.x.foo);  // Logs 'foo!'
}

However, in your example, when you assign x = $('#x') you are in fact changing only the reference of this local x variable to the newly created jQuery object. What you need to do for your example to work is to make both variables reference the same object:

init: function(){
    var self = this,
        x = self.x = $('#x');

    x.on('click', function(){
        console.log('foo!');
    });

    self.x.trigger('click');  // Logs 'foo!'
}
alex
  • 701
  • 5
  • 5