【问题标题】:How to override the closure passed by a deferred object如何覆盖延迟对象传递的闭包
【发布时间】:2013-04-11 00:58:44
【问题描述】:

所以我尝试在网络应用程序上使用 $.Deferred 对象来确保在尝试执行 [目标] 之前已完成 [先决条件]。

长话短说,我想使用的真实代码看起来像......

function QuoteReview(openQuote) {
    //constants
    this.RETIRE_AFTER=180; //...seconds. Do not use cached values older than this, instead, reload them
    this.lastLoadedQuotes=undefined;
    this.quotes=[];
    this.currentQuote={
        chosen:false, 
        id:undefined, 
        loaded:false, 
        name:undefined,
        pricebook:undefined,
        prontoId:undefined,
        state:undefined,
        reviewer:undefined,
        opportunityId:undefined,
        accountCode:undefined,
        items:[],
        lastLoad:undefined 
    };
    var _curQuote=this.currentQuote;
    this.displayQuote=function(forceLoad) {
        //if forceLoad was specified as true, the quote has never been loaded, or the last load is past it's retirement age, load it first
        if (forceLoad || typeof this.currentQuote.lastLoad == "undefined" || ((new Date() - this.currentQuote.lastLoad)/1000)>this.RETIRE_AFTER)
            this.loadQuote().then(this._displayQuote);
        else
            this.displayQuote();
    }
    this._displayQuote=function() {
        console.log(this); //Displays the deferred object
        console.log(self); //reference error (expected)
        console.log(currentQuote); //reference error
        console.log(_curQuote); //reference error
    }
    this.loadQuote=function() {
        if (this.currentQuote.chosen) {
            var self=this;
            return $.getJSON("queries/getQuote.php?id="+encodeURIComponent(this.currentQuote.id),function(data) {
                //TODO add in the error/logged out handling
                data=data.data[0];
                self.currentQuote.name=data.name;
                self.currentQuote.pricebook=data.pricebook;
                self.currentQuote.prontoId=data.prontoId;
                self.currentQuote.state=data.state;
                self.currentQuote.reviewer=data.reviewer;
                self.currentQuote.opportunityId=data.opportunityId;
                self.currentQuote.accountCode=data.accountCode;
                self.currentQuote.items=data.items;
                self.currentQuote.lastLoad=new Date();
                self.currentQuote.loaded=true;
            });
        } else {
            console.log("tried to load quote before it was chosen!");
            return $.Deferred();
        }
    }
}

但我已经编写了一些测试代码来更容易地显示问题:

function getSomeAjax() {
    return $.getJSON("queries/quoteSearch.php?needle=",function(data) {
        console.log("got the response");
        console.log(data);
        window.someInfo=data;
    });
}
function _useIt() {
    console.log("we would use the data here, doing some dom type stuff");
    console.log(window.someInfo);
    console.log(this);
}
function useIt() {
    getSomeAjax().then(_useIt);
}

(该查询只返回一些数据......在这种情况下并不重要,我们不做任何事情)

问题是,当我在_useIt() 函数中记录this 时,我看到了延迟对象的范围。现在这完全有道理,我当时正在打电话 - 延迟对象的成员,但有人知道解决这个问题的方法吗?如果我使用.apply(window,[]),它似乎甚至不会调用_useIt。现在,看到我只是在页面上使用quoteReview = new QuoteReview();,并且我将始终只有一个 QuoteReview 对象,我知道我可以只使用quoteReview.currentQuote 而不是this.currentQuote,如果推我不介意这样做来推,但我也想知道这样做的正确方法。

回答后编辑: 在这种情况下,我最终使用了 Travis 和 Esthete 的答案组合。我发现 Aesthete 在回调函数中为我工作,但后来我意识到我还需要创建一个 _curParts_curQuotes_curPart 变量;我还使用在loadQuote() 中分配的var self=this 来访问匿名ajax 函数中的成员,但这似乎是双重工作(考虑到self 也具有_curQuote 的所有属性)。相反,我在整个构造函数的范围内使用了var self=this,并使用它来访问匿名ajax 函数和作为回调传递给.then() 的函数中的成员。

【问题讨论】:

    标签: javascript jquery closures jquery-deferred


    【解决方案1】:

    您应该能够使用闭包来解决问题,虽然我不认为这是最好的解决方案。

    var _curQuote = this.currentQuote;
    this._displayQuote=function() {
      // Use _curQuote, which should just reference the member currentQuote.
    }
    

    看看this fiddle,希望能消除一些困惑。

    【讨论】:

    • 我不确定这与我所拥有的有什么不同? _displayQuote 函数已经定义在与 currentQuote 变量相同的闭包中,由于被 Deferred.then() 调用,它失去了这个范围。
    • 如果你试试这个方法,你可以访问哪个变量? _curQuote 还是 currentQuote?前者保留在 _displayQuote() 函数的闭包内。
    • @ChrisO'Kelly - 我为你做了一个小提琴,看看它是否有助于理解我所说的关于范围和关闭的内容。
    • 我确实理解,可能不是很好,但我已经阅读了 JS 中的作用域和闭包。我在这里看到的问题是弄清楚你的代码和我的代码之间有什么区别(上面发布)
    • 我认为只有在内部范围内实际访问该变量时,才将变量附加到闭包。所以在函数之外定义var _curQuote = this.currentQuote 是不够的,就像你发现的那样;内部范围内的某些代码需要实际使用该变量才能将其封闭。在您的情况下,在_displayQuote 中使用console.log 导致关闭包含_curQuote。我想如果你把断点放回去,你的 chrome 控制台技术就会起作用。此外,您可以在开发工具的范围变量部分看到它。
    【解决方案2】:

    this 的含义并不总是很明显,我不想猜测它在 _useIt() 中的含义。它可以是全局命名空间或其中的任何内容,具体取决于代码的组织方式。

    抛开this 不谈,代码的第一件事是避免通过全局成员传递data。当getSomeAjax() 返回一个符合Promise 的jqXHR 时,data 会自动传递给链接的.then().done() 处理程序。

    例如,您的代码将简化如下:

    function getSomeAjax() {
        return $.getJSON("queries/quoteSearch.php?needle=", function(data) {
            console.log("got the response");
            console.log(data);
        });
    }
    function _useIt(data) {
        console.log("we would use the data here, doing some dom type stuff");
        console.log(data);
    }
    function useIt() {
        getSomeAjax().then(_useIt);
    }
    

    ...,用匿名写的_useIt 可能更容易理解:

    function getSomeAjax() {
        return $.getJSON("queries/quoteSearch.php?needle=", function(data) {
            console.log("got the response");
            console.log(data);
        });
    }
    function useIt() {
        getSomeAjax().then(function(data) {
            console.log("we would use the data here, doing some dom type stuff");
            console.log(data);
        });
    }
    

    请注意,data 作为匿名函数的第一个参数出现的方式与它作为$.getJSON() 回调的第一个参数出现的方式相同。第二个和第三个参数在这两个函数中同样可用。

    此外,.then() 的强大功能在这里可能不是必需的。 .done() 将完成这项工作,除非您需要通过返回一个新值或一个新的 Promise 来过滤 Promise,以便向下传递到扩展的方法链,例如。 getSomeAjax(...).then(...).then(...).done(...).fail(...).always(...)

    【讨论】:

      【解决方案3】:

      你需要使用闭包和一个新的作用域变量。 this 取决于调用上下文,并将分配给您所看到的 jQuery 对象。

      您可以尝试这样的事情(从您的代码中截取):

      this.loadQuote=function() {
          var self = this; // This is grabbing the correct context
          return $.getJSON("local/address.php",function(data){
              //success handler. Fills in the this.currentQuote object
              // Here you'll want to use self.currentQuote
          });
      }
      

      编辑:我想提供一些样本来清理一些 cmets。我个人的偏好是 Underscore.js 绑定方法。如果您不使用该框架,则可以包含您自己的该方法版本并将其扔到函数原型上以进行更简洁的调用。

      var MyClass = function() {
          this.MyVariable = "Hello World";
      
          this.MyCallback = function(data) {
              alert(this.MyVariable);
          };
      
          // Using 'self' variable to preserve invocation context
          //    The important thing is that 'self' is LOCAL ONLY
          //    and the MyCallback method can reliably rely on 'this'
          this.MakeCallSelfVariable = function() {
              var self = this;
              return $.getJSON("url", function(data) {
                  self.MyCallback(data);
              }
          };
      
          // Using Underscore.js _.bind()
          this.MakeCallUnderscore = function() {
              return $.getJSON("url", _.bind(this.MyCallback, this));
          };
      
          // Using jQuery's options to set the context (obviously
          //    depends on jQuery
          this.MakeCallJQuery = function() {
              return $.ajax({
                  dataType: "json",
                  url: "url",
                  context: this, // Important line
                  success: function(data) {
                      // Here, 'this' will refer to what you
                      //    assigned to context
                      this.MyCallback(data);
                  }
              });
          };
      };
      

      【讨论】:

      • 嘿,这解决了一个我还没有注意到的问题(即,在 ajax 成功处理程序中访问它),但不适用于 _displayQuote 的问题。我正在用我的代码的完整版本更新问题,以防更有帮助。
      • var self = this; 方法肯定会起作用,无论您如何使用回调,这就是为什么我没有费心区分它们的原因。我在您的其他 cmets 中注意到您可能发现调试问题,我相信这与此评论有关。我在上面添加了一些额外的示例,它们都实现了相同的结果(在回调中保留上下文)。
      猜你喜欢
      • 2016-07-24
      • 2020-07-10
      • 2013-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多