如何避免从闭包中访问可变变量

时间:2012-12-11 04:06:07

标签: debugging node.js closures

我有一些像这样的代码:

for(var id=0; id < message.receiver.length; id++){
   var tmp_id = id;
   zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
   pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
   delete pushStatusPool[message.receiver[tmp_id]];
   ...
   });
}

我收到警告说在关闭时使用tmp_id可能会导致问题,因为它是一个可变变量。

我怎么能避免这种情况?我的意思是如何将不可变变量发送回回调,因为这是for循环而我无法更改zlib.gzip的代码?或者换句话说,我怎么能把一个参数传递给一个闭包?

6 个答案:

答案 0 :(得分:41)

您需要创建一个范围,以使用自执行功能正确捕获tmp_id。那是因为整个for循环是一个范围,意味着每次通过,你都捕获相同的变量。所以回调最终会出现错误的ID,因为在调用回调之前temp_id的值会被更改。

我忽略(或关闭)警告,但似乎抱怨因为temp_id是可变的,你可能会重新分配它。那有点傻。如果您确实想要修复它,请尝试使用const关键字而不是var

for(var id=0; id < message.receiver.length; id++){
   (function(){
       const tmp_id = id;
       zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
           pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
           delete pushStatusPool[message.receiver[tmp_id]];
           ...
       });
   })();
}

答案 1 :(得分:10)

我遇到了同样的问题并通过将id传递给闭包来解决了稍微修改user24359的答案:

for(var id=0; id < message.receiver.length; id++){
   (function(tmp_id){
       zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
           pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
           delete pushStatusPool[message.receiver[tmp_id]];
           ...
       });
   })(id);
}

答案 2 :(得分:2)

这里简化了user24359的绝佳答案。 这是解决方案:

var object = {a:1,b:2};

for (var y in object){
    (function(){const yyy = y;
        setTimeout(function(){console.log(yyy)},3000);})();
}

上面的代码记录了一个b并且是解决方案。 以下代码记录b b:

var object = {a:1,b:2};
for (var y in object){

    setTimeout(function(){console.log(y)},3000);
}

答案 3 :(得分:0)

我在量角器中遇到了同样的问题。使用以下代码解决它 -

(function(no_of_agents){
              ptor.element.all(by.repeater('agent in agents').column('displayName')).then(function(firstColumn){
                    console.log(i, '>>>>>Verifying the agent Name');
                    var agentsSorted = sortAgentsByName();
                    //verify the agent name
                    expect(firstColumn[no_of_agents].getText()).toEqual(agentsSorted[no_of_agents].name);
                    //now click on the agent name link
                    firstColumn[no_of_agents].click();
                    ptor.sleep(5000);
              });
            })(no_of_agents);

答案 4 :(得分:0)

@ user24359 answer是一个不错的解决方案,但您只需使用 Comparator<OfferInfo> comparator = new ComparaterClosesyTOMe(); public class ComparaterClosesyTOMe implements Comparator<OfferInfo> { @Override public int compare(OfferInfo arg0, OfferInfo arg1) { double distance = Utils.getDistance(gpsTracker.getLatitude(), gpsTracker.getLongitude(), arg0.latitude, arg0.longitude, "M"); double distance1 = Utils.getDistance(gpsTracker.getLatitude(), gpsTracker.getLongitude(), arg1.latitude, arg1.longitude, "M"); if (distance > distance1) return 1; else return -1; } } 关键字替换var关键字。

let

变为

for(var id=0;

详见here

编辑:正如HeribertoJuárez建议的那样,它只适用于支持EcmaScript6的浏览器。

答案 5 :(得分:0)

vartmp_id)位于回调函数的较高范围内的循环中创建闭包是common mistake,应该避免var块状范围。因此,并且因为在循环中创建的每个闭包共享相同的lexical environment,所以当回调函数时,变量将始终是最后一个迭代值(即message.receiver.length - 1tmp_id)被调用。您的IDE会检测到此行为并正确地抱怨。

为了避免警告,有几种解决方案:

  • var替换为 let ,确保每个创建的闭包在每次迭代中都定义了自己的作用域tmp_id

    for (var id = 0; id < message.receiver.length; id++) {
      let tmp_id = id;
      zlib.gzip(JSON.stringify(message.json), function(err, buffer) {
        // Do something with tmp_id ...
      });
    }
    
  • 利用IIFEgennadi.w did来创建词法环境。

  • 使用工厂函数(createCallback)在每次迭代中创建一个回调函数:

    const createCallback = tmp_id => function(err, buffer) {
      // Do something with tmp_id ...
    };
    for (var id = 0; id < message.receiver.length; id++) {
      zlib.gzip(JSON.stringify(message.json), createCallback(id));
    }
    
  • bind回调函数中的变量,它们将预先添加到其参数中:

    for (var id = 0; id < message.receiver.length; id++) {
      zlib.gzip(JSON.stringify(message.json), function(tmp_id, err, buffer) {
        // Do something with tmp_id (passed as id) ...
      }.bind(this, id));
    }
    

如果可能的话,从ECMAScript 2015开始应该避免使用var,因为这种容易出错的行为。