Scoping problem with Javascript callback

2019-03-01 08:22发布

问题:

I am having some trouble getting a callback function to work. Here is my code:

SomeObject.prototype.refreshData = function()
{
  var read_obj = new SomeAjaxCall("read_some_data", { }, this.readSuccess, this.readFail);
}

SomeObject.prototype.readSuccess = function(response)
{
    this.data = response;
    this.someList = [];
    for (var i = 0; i < this.data.length; i++)
    {
      var systemData = this.data[i];
      var system = new SomeSystem(systemData);
      this.someList.push(system);
    }
    this.refreshList();
}

Basically SomeAjaxCall is making an ajax request for data. If it works we use the callback 'this.readSuccess' and if it fails 'this.readFail'.

I have figured out that 'this' in the SomeObject.readSuccess is the global this (aka the window object) because my callbacks are being called as functions and not member methods. My understanding is that I need to use closures to keep the 'this' around, however, I have not been able to get this to work.

If someone is able show me what I should be doing I would appreciate it greatly. I am still wrapping my head around how closures work and specifically how they would work in this situation.

Thanks!

回答1:

Well the most straightforward thing to do is to just wrap "this.readSuccess" in another function:

SomeObject.prototype.refreshData = function()
{
  var obj = this;
  var read_obj = new SomeAjaxCall("read_some_data", { }, 
    function() { obj.readSuccess(); }, function() { obj.readFail(); });
}

Some Javascript frameworks provide a utility to "bind" a function to an object, which simply means that it creates one of those little functions for you. Note that the variable "obj" will be "remembered" by those little functions, so when your handlers are called the "this" reference will be to the object that was used to call "refreshData".



回答2:

Your problem here is not exactly a closure or scoping problem. The problem is that when you assign this.readSuccess to a variable, you assign the function itself without any notion of the object it originaly belongs to.

In the same way, you can take a regular, "stand-alone" function and use it as method of an object:

function hello() {
    alert("Hello "+this.planet);
}
var planet = "Earth";
hello(); // -> 'Hello Earth'

var Venus = {
    planet: "Venus"
};
hello.apply(Venus); // -> 'Hello Venus'
Venus.hello = hello;
Venus.hello(); // -> 'Hello Venus'

And your problem can be replicated in this example

var helloVenus = Venus.hello;
helloVenus(); // -> 'Hello Earth'

So your problem is to assign this.readSuccess to some variable and having it called as a method of this. Which can be done with a closure as demonstrated by Pointy. Since I don't know what "SomeAjaxCall" actually does, it's hard to know if the value of this is actually lost and if var obj = this is actually needed. Chances are that it's not, so you can be fine with this kind of code:

var helloVenus = function() { Venus.hello() }
helloVenus(); // -> 'Hello Venus'

In your case, that would be (edit: adding the arguments passed to the handler) :

SomeObject.prototype.refreshData = function()
{
  var read_obj = new SomeAjaxCall(
    "read_some_data",
    { },
    function () { this.readSuccess.apply(this, arguments) },
    function () { this.readFail.apply(this, arguments) }
  );
}

As noted previously, several js frameworks offer a bind function to simplify this kind of issue. But you don't need a complete framework just for this : here is a perfectly fine Function#bind method that works an plain javascript:

Function.prototype.bind = function(obj) {
    var __method = this;
    var args = [];
    for(var i=1; i<arguments.length; i++)
        args.push(arguments[i]);
    return function() {
        var args2 = [];
        for(var i=0; i<arguments.length; i++)
            args2.push(arguments[i]);
        return __method.apply(obj, args.concat(args2));
    };
}

With the help of Function#bind, you can write:

SomeObject.prototype.refreshData = function()
{
  var read_obj = new SomeAjaxCall(
    "read_some_data",
    { },
    this.readSuccess.bind(this),
    this.readFail.bind(this)
  );
}


回答3:

The following site seems to suggest that the problem may be more in your "class" building than in the usage. If you read down to the "rewrite using prototype properties", they write that that particular method of "class" structuring will keep the methods global instead of instance-based. Perhaps another creation method?

http://devedge-temp.mozilla.org/viewsource/2001/oop-javascript/