【问题标题】:How to avoid access mutable variable from closure如何避免从闭包中访问可变变量
【发布时间】:2012-11-28 14:31:09
【问题描述】:

我有一些这样的代码:

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 的代码?或者换句话说,我如何将参数传递给闭包?

【问题讨论】:

  • 如何避免?你的问题不清楚。请更具体地说明您需要帮助的内容。
  • 我开始写回复......但很明显这可以使用重构:(你在压缩同一件事message.receiver.length 次。发布整个事情?

标签: debugging node.js closures


【解决方案1】:

您需要创建一个范围以使用自执行函数正确捕获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]];
           ...
       });
   })();
}

【讨论】:

  • 谢谢!这就是我想要的。我使用的是 JetBrains 生产的webstorm
  • 在他的代码中,他没有捕获任何变量——javascript 具有函数作用域,而不是块作用域。回调中的 tmp_id 变量会根据执行时间而有所不同。在您的示例中,实际上并不需要 const 关键字... var 可以解决问题:)
  • @brianreavis 关于执行时间的要点。我没有注意到gzip 会异步执行,因此函数范围至关重要。会编辑。我对 Webstorm 一无所知,但没有const,OP 仍然会捕获一个可变变量(即我可以在调用gzip 后更改tmp_id),它听起来就是这样警告正在抱怨。
  • @brianreavis - 不,这里是可变或不可变的 变量,它是被捕获的变量,因此与值与引用类型无关。为了说明这里的问题,试试这个:jsfiddle.net/rD82j,并观察它在控制台中打印的内容。然后将var 更改为const 并再次运行。 这就是警告试图帮助解决的问题。
  • 我并不是说该变量是不可变的——我认为const 在这种情况下或任何情况下都没有用。这是不好的做法(它使代码在其他 ECMAScript 变体中不兼容)并且不应该仅仅为了让 IDE 满意而使用。
【解决方案2】:

我遇到了同样的问题,并通过将 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);
}

【讨论】:

  • 我喜欢这种方法
【解决方案3】:

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

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

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

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

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

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      @user24359 答案是一个很好的解决方案,但您可以简单地将 var 关键字替换为 let 关键字。

      for(var id=0;
      

      变成

      for(let id=0;
      

      查看详情here

      编辑:正如 Heriberto Juárez 所建议的,它只适用于支持 EcmaScript6 的浏览器。

      【讨论】:

      • 这不会在不支持 ECMAScript6 的浏览器中引起问题吗?
      【解决方案6】:

      var (tmp_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 ...
          });
        }
        
      • 利用IIFE(如gennadi.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,因为这种行为容易出错。

      【讨论】:

        猜你喜欢
        • 2014-10-27
        • 2015-09-18
        • 1970-01-01
        • 1970-01-01
        • 2011-04-12
        • 1970-01-01
        • 2017-01-20
        • 2018-12-24
        相关资源
        最近更新 更多