【问题标题】:Pass extra parameter to jQuery getJSON() success callback function [duplicate]将额外参数传递给jQuery getJSON()成功回调函数[重复]
【发布时间】:2011-09-02 00:24:07
【问题描述】:

我以前从未使用过回调函数,所以我可能犯了一个完全愚蠢的错误。我想我有点理解这里的问题,但不知道如何解决它。

我的代码(有点简化)是:

for (var i = 0; i < some_array.length; i++) {
    var title = some_array[i];
    $.getJSON('some.url/' + title, function(data) {
        do_something_with_data(data, i);
    }

据我所知,这个匿名函数只有在 getJSON() 接收到数据时才会被调用。但到目前为止,i 没有我需要的值。或者,据我观察,它具有循环完成后的最后一个值(不应该超出范围吗?)。

因此,如果数组的大小为 6,do_something_with_data() 将被调用五次,值为 5。

现在我想,只需将i 传递给匿名函数

function(data, i) { }

但这似乎是不可能的。 i 现在未定义。

【问题讨论】:

    标签: javascript jquery json


    【解决方案1】:

    您可以使用返回另一个函数的立即函数(立即执行的函数)创建一个闭包:

    for (var i = 0; i < some_array.length; i++) {
        var title = some_array[i];
        $.getJSON('some.url/' + title, (function() {
            var ii = i;
            return function(data) {
               do_something_with_data(data, ii);
            };
        })());
    }
    

    【讨论】:

    • 我试图调整它,但是 data 现在从哪里获得它的价值呢?现在对我来说是未定义的。
    • 本来打算发布原始代码不起作用的,但我看到您已修复它。请注意,jslint 会要求您将 var 声明移到循环外,并将匿名函数调用放在括号内,而不是放在外面。
    • Chris - 我已经对其进行了更新,现在数据是返回函数的参数。
    • 太好了,这行得通。现在我有点难以选择正确的答案。你的对我有用,但 Erik 详细解释了关闭。
    • 您可以自行选择答案,但请务必对您认为有帮助的任何答案进行投票。
    【解决方案2】:

    每次创建 N 个闭包并传入 'i' 的值,如下所示:

    var i, title;
    for (i = 0; i < some_array.length; i++) {
        title = some_array[i];
        $.getJSON('some.url/' + title, (function(i_copy) {
            return function(data) {
                do_something_with_data(data, i_copy);
            };
        })(i));
    }
    

    【讨论】:

    • 请注意,jslint 会要求您将 var 声明移到循环之外,并将匿名函数调用放在括号内,而不是放在括号外。
    【解决方案3】:

    您需要了解 闭包 是什么。在 JavaScript 中,每个变量的作用域都有一定的规则。

    • 隐式声明或使用var 声明的变量的范围是最近/当前function(包括“箭头函数”),或者如果不在函数中,则为window 或其他适合执行的全局对象上下文(例如,在 Node 中,global)。
    • 使用letconst(在ES5 及更高版本中)声明的变量的作用域是最近的语句块{ /* not an object, but any place that will take executable statements here */ }

    如果任何代码可以访问当前作用域或任何父作用域中的变量,则会在该变量周围创建一个闭包,使变量保持活动状态并保持变量引用的任何对象实例化,以便这些父或内部函数或块可以继续引用变量并访问该值。

    因为原始变量仍然处于活动状态,如果您稍后在代码中更改该变量的值anywhere,那么当稍后运行对该变量具有闭包的代码时,它将具有更新/更改value,不是第一次创建函数或作用域时的值。

    现在,在我们解决使闭包正常工作之前,请注意,在循环中重复声明 title 变量而不使用 letconst 是行不通的。 var 变量被提升到最近的函数的作用域中,并且没有分配var 且不引用任何函数作用域的变量被隐式附加到全局作用域,即window in a浏览器。在constlet 存在之前,JavaScript 中的for 循环没有作用域,因此在其中声明的变量实际上只声明一次,尽管似乎在循环内(重新)声明了。在循环外声明变量应该有助于您了解为什么您的代码没有按预期工作。

    事实上,当回调运行时,因为它们对同一个变量 i 有一个闭包,所以它们都会在 i 递增时受到影响,并且它们都将使用 @ 的 current 值987654339@ 当它们运行时(您发现这是不正确的,因为回调都在循环完全完成创建它们之后运行)。异步代码(例如 JSON 调用响应)在所有同步代码完成执行之前不会也不能运行——因此循环保证在任何回调执行之前完成。

    要解决这个问题,您需要运行一个具有其自己的范围的新函数,以便在循环内声明的回调中,每个不同都有一个新的闭包em> 值。您可以使用单独的函数来执行此操作,或者仅在回调参数中使用调用的匿名函数。这是一个例子:

    var title, i;
    for (i = 0; i < some_array.length; i += 1) {
        title = some_array[i];
        $.getJSON(
           'some.url/' + title,
           (function(thisi) {
              return function(data) {
                 do_something_with_data(data, thisi);
                 // Break the closure over `i` via the parameter `thisi`,
                 // which will hold the correct value from *invocation* time.
              };
           }(i)) // calling the function with the current value
        );
    }
    

    为清楚起见,我将把它分解成一个单独的函数,以便您了解发生了什么:

    function createCallback(item) {
       return function(data) {
          do_something_with_data(data, item);
          // This reference to the `item` parameter does create a closure on it.
          // However, its scope means that no caller function can change its value.
          // Thus, since we don't change `item` anywhere inside `createCallback`, it
          // will have the value as it was at the time the createCallback function
          // was invoked.
       };
     }
    
    var title, i, l = some_array.length;
    for (i = 0; i < l; i += 1) {
        title = some_array[i];
        $.getJSON('some.url/' + title, createCallback(i));
        // Note how this parameter is not a *reference* to the createCallback function,
        // but the *value that invoking createCallback() returns*, which is a function taking one `data` parameter.
    }
    

    注意:由于您的数组中显然只有标题,您可以考虑使用title 变量而不是i,这需要您返回some_array。但无论哪种方式都有效,您知道自己想要什么。

    考虑这一点的一种可能有用的方法是,回调创建函数(匿名函数或createCallback 函数)本质上将i 变量的 转换为单独的@ 987654347@ 变量,通过每次引入一个具有自己作用域的新函数。或许可以说“参数打破了闭包中的值”。

    请注意:如果不复制对象,此技术将无法对对象起作用,因为对象是引用类型。仅仅将它们作为参数传递不会产生事后无法更改的东西。您可以随意复制街道地址,但这不会创建新房子。如果你想要一个通往不同地方的地址,你必须建造一座新房子。

    【讨论】:

      【解决方案4】:

      我认为有些浏览器无法同时进行多个异步调用,因此您可以一次调用一个:

      var i;
      function DoOne(data)
      {
          if (i >= 0)
              do_something_with_data(data, i);
          if (++i >= some_array.length)
              return;
          var title = some_array[i];
          $.getJSON('some.url/' + title, DoOne);
      }
      
      // to start the chain:
      i = -1;
      DoOne(null);
      

      【讨论】:

      • 不要使用async:false。它会导致 整个 浏览器(包括 UI 线程)在网络请求期间锁定。如果您冻结了他们的浏览器,您的用户会非常不高兴。
      • Firefox 和 Chrome 只是锁定了当前选项卡,但重点是,我从答案中删除了它。
      【解决方案5】:

      如果您可以修改some.url 处的服务,最好很多最好不要为some_array 中的每个项目单独发出 HTTP 请求,而只需传递数组中的每个项目在单个 HTTP 请求中。

      $.getJSON('some.url', { items: some_array }, callback);
      

      您的数组将被 JSON 序列化并发布到服务器。假设some_array 是一个字符串数组,请求将如下所示:

      POST some.url HTTP/1.1
      ...
      
      {'items':['a','b','c', ... ]}
      

      然后,您的服务器脚本应反序列化来自请求正文的 JSON 请求,并遍历 items 数组中的每个项目,返回一个 JSON 序列化的响应数组。

      HTTP/1.1 200 OK
      ...
      
      {'items':[{id:0, ... }, {id:1, ... }, ... ]}
      

      (或您返回的任何数据。)如果您的响应项与请求项的顺序相同,则很容易将它们重新组合在一起。在您的成功回调中,只需将项目索引与some_array 的索引匹配即可。把它们放在一起:

      $.getJSON('some.url', { items: some_array }, function(data) {
          for (var i = 0; i < data.items.length; i++) {
              do_something_with_data(data.items[i], i);
          }
      });
      

      通过像这样将您的请求“分批”成一个 HTTP 请求,您将显着提高性能。考虑一下,如果每个网络往返至少需要 200 毫秒,有 5 个项目,那么您会看到至少 1 秒的延迟。通过一次请求它们,网络延迟保持恒定 200 毫秒。 (显然,对于较大的请求,服务器脚本执行和网络传输时间将发挥作用,但性能仍将比为每个项目发出单独的 HTTP 请求好一个数量级。)

      【讨论】:

      • 这些都是好点。最好结合请求。
      【解决方案6】:

      我遇到了与 OP 完全相同的问题,但以不同的方式解决了它。我用 jQuery $.each 替换了我的 JavaScript 'for' 循环,它为每次迭代调用一个函数,我认为该函数可以解决回调“计时”问题。我将我的外部数据数组组合成一个 JavaScript 对象,这样我就可以引用我在 JSON URL 上传递的参数和该对象的同一元素中的另一个字段。我的对象元素来自使用 PHP 的 mySQL 数据库表。

      var persons = [
       { Location: 'MK6', Bio: 'System administrator' },
       { Location: 'LU4', Bio: 'Project officer' },
       { Location: 'B37', Bio: 'Renewable energy hardware installer' },
       { Location: 'S23', Bio: 'Associate lecturer and first hardware triallist' },
       { Location: 'EH12', Bio: 'Associate lecturer with a solar PV installation' }
      ];
      
      function initMap() {
        var map = new google.maps.Map(document.getElementById('map_canvas'), {
          center: startLatLon,
          minZoom: 5,
          maxZoom: 11,
          zoom: 5
        });
        $.each(persons, function(x, person) {
          $.getJSON('http://maps.googleapis.com/maps/api/geocode/json?address=' + person.Location, null, function (data) {
            var p = data.results[0].geometry.location;
            var latlng = new google.maps.LatLng(p.lat, p.lng);
            var image = 'images/solarenergy.png';
            var marker = new google.maps.Marker({
              position: latlng,
              map: map,
              icon: image,
              title: person.Bio
            });
            google.maps.event.addListener(marker, "click", function (e) {
              document.getElementById('info').value = person.Bio;
            });
          });
        });
      }
      

      【讨论】:

        猜你喜欢
        • 2014-03-25
        • 1970-01-01
        • 1970-01-01
        • 2017-04-09
        • 1970-01-01
        相关资源
        最近更新 更多