Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Object Properties go to undefined after ajax request returns

if you have an object and set a property for it, you can access that property in a function called on that object. but if you call a function and do an ajax request such that a different function is called from onreadystatechange, that secondary response function does not have access to the property. Thats a little confusing so see what I mean here. The property this.name is the one that changes.

//from W3Schools website
function getXHR(){if (window.XMLHttpRequest){return new XMLHttpRequest();}if (window.ActiveXObject){return new ActiveXObject("Microsoft.XMLHTTP");}return null;}

function TestObject()
{
    this.name = "";            //public
    var xhr = null;            //private
    var response = function()  //private
    {
        if(xhr.readyState > 3)
        {
            alert("B: my name is " + this.name);
        }
    }
    this.send = function()     //public
    {
        alert("A: my name is " + this.name);
        if(xhr === null)
        {
            xhr = getXHR();
        }
        var url = "http://google.com";
        xhr.onreadystatechange = response;
        xhr.open("GET", url, true);
        xhr.send(null);          
    }
}
var o = new TestObject();
o.name = "Ice Cube";
o.send();

Results are:

A: my name is IceCube
B: my name is undefined

If response is public this happens as well. If xhr is public this also happens. Something occurs so that the response function called doesnt have access to the same parameters.

like image 430
adasdas Avatar asked Jun 17 '10 16:06

adasdas


1 Answers

this works very differently in JavaScript than it does in some other languges, like C# or Java. The value of this within a function is set entirely by how a function is called, not where the function is defined. More here: You Must Remember 'this'.

When a function is just called in the "normal" way:

foo();

...then within the function, this will always reference the global object (on browsers, the global object is window). There's nothing about a function on an object instance that "ties" it to that object. For instance:

var obj = {
    name: "Joe",
    speak: function() {
        alert("I'm " + this.name);
    }
};
obj.speak(); // alerts "I'm Joe"
var f = obj.speak;
f();         // alerts "I'm " (this.name is undefined within the call)

So to get an event handler called with the right "context" (this value), you have to take special steps, as outlined in the post linked above.

In your particular case, since you're already defining closures for your functions, you can readily handle it with a variable that your closures will close over:

function TestObject()
{
    var self;

    // Set up a variable referencing `this`
    self = this;

    // From here on out, use it instead of `this`

    self.name = "";            //public
    var xhr = null;            //private
    var response = function()  //private
    {
        if(xhr.readyState > 3)
        {
            alert("B: my name is " + self.name);
        }
    }
    self.send = function()     //public
    {
        alert("A: my name is " + self.name);
        if(xhr === null)
        {
            xhr = getXHR();
        }
        var url = "http://google.com";
        xhr.onreadystatechange = response;
        xhr.open("GET", url, true);
        xhr.send(null);
    }
}

More on closures in this article, but basically, that works because the self variable is available to the functions defined within the TestObject constructor. You don't use this, so you don't worry about making sure that the event handlers get called in a way that sets this correctly.

There are reasons you may not want to use those closures (memory impact, if you create a lot of TestObjects, because each and every TestObject gets its own copy of every function), but since that's already how you've defined the object, there's virtually no cost in making use of them. In this particular case, I'm guessing you're not creating thousands of these XHR responders.

like image 181
T.J. Crowder Avatar answered Oct 20 '22 00:10

T.J. Crowder