JavaScript 对象属性在 ajax 请求返回后变为 undefined。

5

如果你有一个对象并为其设置属性,你可以在调用该对象的函数中访问该属性。但是,如果你调用一个函数并执行 ajax 请求,从 onreadystatechange 调用不同的函数,那么次要响应函数将无法访问该属性。这有点令人困惑,请参见下面的示例。该属性 this.name 是发生变化的属性。

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

结果如下:

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

如果响应是公共的,那么也会发生这种情况。如果xhr也是公共的,那么也会发生此问题。有些事情发生了,导致调用响应函数时无法访问相同的参数。
3个回答

5

this在JavaScript中与其他语言(如C#或Java)的工作方式非常不同。函数内部this的值完全由函数的调用方式确定,而不是函数定义的位置。更多信息请参见:记住'this'

当函数以“正常”的方式被调用时:

foo();

...然后在函数内部,this 总是引用全局对象(在浏览器中,全局对象是 window)。 对象实例上的函数与该对象之间没有任何“连接”。例如:

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)

为了让事件处理程序使用正确的“上下文”(this值)被调用,您需要采取特殊步骤,如上面链接的文章所述。
在您的特定情况下,由于您已经为函数定义了闭包,因此您可以轻松地通过一个变量来处理它,该变量将被您的闭包封闭。
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);
    }
}

关于闭包的更多信息在这篇文章中, 总的来说,这是因为self变量对于在TestObject构造函数内定义的函数是可用的。您不使用this,因此无需担心以正确设置this方式调用事件处理程序。

有些情况下您可能不想使用这些闭包(内存影响,如果您创建了大量的TestObject,因为每个TestObject都会获得自己的副本),但既然已经定义了对象,那么利用它们几乎没有成本。在这种特殊情况下,我猜您不会创建成千上万个这样的XHR响应器。


3

window 拥有 onreadystatechange 方法,因此在您的回调函数中,this 指的是 window

您可以在 TestObject 的函数体中保存实例,例如:var that = this,然后使用 that.name 代替 this.name。这将绑定变量到您的回调函数,以便它将记住自己的实例。

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

function TestObject()
{
    var that = this;
    that.name = "";            //public
    var xhr = null;            //private
    var response = function()  //private
    {
        if(xhr.readyState > 3)
        {
            alert("B: my name is " + that.name);
        }
    }
    this.send = function()     //public
    {
        alert("A: my name is " + that.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();

这是通常接受的做法吗?它有点像一个hack,也有点像一个酷炫的技巧。 - adasdas
这不是黑客行为,而是语言的简单工作方式。http://www.jibbering.com/faq/notes/closures/ - meder omuraliev
好的,这样做很有道理。我会使用另一个人所命名的“self”,因为那更加合理。 - adasdas
我不想让你感到困惑,因为self是一个特殊的全局属性,也指向窗口,尽管在函数上下文中变量会覆盖它,但这样会更加混乱,是的... - meder omuraliev
@adasdas - 这是正确的方法。我认为惯例是使用 that 而不是 self,尽管我更喜欢 self - Hooray Im Helping

0

这是因为您的response函数实际上没有在您的TestObject对象上调用。

您知道,在JavaScript中,函数与对象并不是严格相关的,就像您在C++、C#、Java或几乎任何其他语言中看到的那样。相反,函数实际上是单独的对象,存在于它们自己之中,并且它们具有这个有趣的属性,即您可以在任何对象上调用任何函数。

例如:

var obj1 = { Prop1: "Property 1 Value!" };
var obj2 = { Prop2: "Property 2 Value!" };
var myFunc = function() { alert( "Prop1 = " + this.Prop1 + ", Prop2 = " + this.Prop2 ); };

obj1.fn = myFunc; // Assign myFunc to property obj1.fn
obj1.fn();        // Call myFunc with this=obj1

// Similarly for obj2:
obj2.somename = myFunc;
obj2.somename();

输出将是:

Prop1 = Property1 Value!, Prop2 = undefined
Prop1 = undefined, Prop2 = Property 2 Value!

看到我在这里做了什么了吗?函数myFunc独立存在,没有附加到任何特定的对象上。而我将它赋值给不同对象的属性。

函数是一个,就像字符串或整数一样。我可以这样做:

var str = "Some String";
obj1.someprop = str;

而且我也可以轻松地做到这一点:

var myFunc = function() {}
obj1.someotherprop = myFunc;

现在,当某个对象的某个属性的值是一个函数时,您可以调用该函数,并且它将在该对象上被调用 - 这意味着this将是对该对象的引用。但是,如果您在不使用obj1.前缀的情况下调用相同的函数,则不会在该对象上调用该函数。例如:
var obj1 = {};
obj1.Prop = "Value";
obj1.Func = function() { alert( this.Prop ); }

obj1.Func();        // Displays "Value"

var f = obj1.Func;
f();                // Displays "undefined"

回到你最初的问题:你的问题是你的response函数没有在你的TestObject对象上被调用。因此,你必须在一个本地变量中保存对该对象的引用,并使用它来代替this。像这样:
function TestObject()
{
    this.name = "";            //public
    var xhr = null;            //private

    var _this = this;
    var response = function()  //private
    {
        if(xhr.readyState > 3)
        {
            alert("B: my name is " + _this.name);
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接