Object literal root property is undefined when used from within another property's function

StackOverflow https://stackoverflow.com/questions/16160025

Вопрос

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);
}
Это было полезно?

Решение

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));

Другие советы

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!'
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top