【问题标题】:javascript variable scope/closure in loop after timeout超时后循环中的javascript变量范围/闭包
【发布时间】:2012-01-27 04:33:40
【问题描述】:

时间不早了,Douglas Crockford 居住的大脑部分已经关闭。我尝试了一些事情,但没有按预期进行。

我有一个画布,我在其中绘制了 2 条线,然后在计时器上将它们淡出,但只有循环中的最后一行被淡出。这是我的小提琴,向下看 JS 中的第 50 行,查看它的实际效果,在右下窗格中拖动鼠标:

http://jsfiddle.net/mRsvc/4/

这是函数,基本上超时只获取循环中的最后一个值,我以前见过这个,我敢肯定如果我不是那么神志不清,它可能会更简单。这里是特别的功能:

function update()
        {
            var i;
            this.context.lineWidth = BRUSH_SIZE;            
            this.context.strokeStyle = "rgba(" + COLOR[0] + ", " + COLOR[1] + ", " + COLOR[2] + ", " +  BRUSH_PRESSURE + ")";
            for (i = 0; i < scope.painters.length; i++)
            {
                scope.context.beginPath();
                var dx = scope.painters[i].dx;
                var dy = scope.painters[i].dy;
                scope.context.moveTo(dx, dy);   
                var dx1 = scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.mouseX) * scope.painters[i].div) * scope.painters[i].ease;
                scope.painters[i].dx -= dx1;
                var dx2 = scope.painters[i].dx;
                var dy1 = scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.mouseY) * scope.painters[i].div) * scope.painters[i].ease;
                scope.painters[i].dy -= dy1;
                var dy2 = scope.painters[i].dy;
                scope.context.lineTo(dx2, dy2);
                scope.context.stroke();
                for(j=FADESTEPS;j>0;j--)
                {
                    setTimeout(function()
                        {
                            var x=dx,y=dy,x2=dx2,y2=dy2;
                            scope.context.beginPath();
                            scope.context.lineWidth=BRUSH_SIZE+1;
                            scope.context.moveTo(x, y);
                            scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
                            scope.context.lineTo(x2, y2);
                            scope.context.stroke();
                            scope.context.lineWidth=BRUSH_SIZE;
                        },
                    DURATION/j);
                }
            }
        }

【问题讨论】:

标签: javascript html canvas scope


【解决方案1】:

问题在于,您在传递给setTimeout() 的函数中引用的变量dxdy 等是在周围范围内定义的,并且当任何超时实际运行这些变量时保存循环最后一次迭代的值。

您需要创建一个额外的包含函数来关闭每次迭代的值。尝试以下方法:

for(j=FADESTEPS;j>0;j--) {
   (function(x,y,x2,y2) {
      setTimeout(function() {
         scope.context.beginPath();
         scope.context.lineWidth=BRUSH_SIZE+1;
         scope.context.moveTo(x, y);
         scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
         scope.context.lineTo(x2, y2);
         scope.context.stroke();
         scope.context.lineWidth=BRUSH_SIZE;
      },
      DURATION/j);
   })(dx, dy, dx2, dy2);
}

这会为j=FADESTEPS 循环的每次迭代创建一个新的匿名函数,立即执行它并传递dx 等在每次循环迭代运行时的值,并移动@987654327 @、y 等变量从现有函数中取出,并将它们作为新函数的参数,这样到超时运行时,它将使用正确的值。

【讨论】:

    【解决方案2】:

    你可以试试这样的:

    `<script>
    for(j=10;j>0;j--)
                    {
                    var fn = function(ind){return function()
                            {
                                console.log(ind);
                            };
                            }(j);
                        setTimeout(fn,
                        1000);
                    }
    </script>`
    

    【讨论】:

      【解决方案3】:

      或者另一种方式(只要你不使用IE,而是让它先学习canvas :))

      for(j=FADESTEPS;j>0;j--)
      {
         setTimeout(function(x,y,x2,y2)
           {
              scope.context.beginPath();
              scope.context.lineWidth=BRUSH_SIZE+1;
              scope.context.moveTo(x, y);
              scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
              scope.context.lineTo(x2, y2);
              scope.context.stroke();
              scope.context.lineWidth=BRUSH_SIZE;
           },
           DURATION/j,dx,dy,dx2,dy2);
      }
      

      ps:不需要额外的功能(原因很清楚)

      【讨论】:

        【解决方案4】:
        1. 首先j 是一个全局变量。
        2. 其次,你永远不会关闭你开始的路径,这会导致内存泄漏。它似乎真的很慢,这可能就是原因。完成以beginPath() 开头的路径后,您需要致电closePath()
        3. 接下来,我认为它的工作原理有一些普遍的有趣之处。你通过用白色画出最后一个东西来淡出。我以前做过类似的事情,但是我清除了整个屏幕并一遍又一遍地画东西。这对我来说没问题。

        说明

        关于dxdy 从更高范围传递的其他答案是正确的答案。在同步 for 循环中定义的异步函数将采用最新版本的状态。

        for (var i = 0; i < 10; i++) setTimeout(function() { console.log(i)}, 10 )
        10
        10
        // ...
        

        【讨论】:

          【解决方案5】:

          我建议您使用数组并存储点,以避免在循环中调用 setTimeOut。有点像这样。

              this.interval = setInterval(update, REFRESH_RATE);
          
              var _points = [];
          
              function update() {
                  var i;
                  this.context.lineWidth = BRUSH_SIZE;
                  this.context.strokeStyle = "rgba(" + COLOR[0] + ", " + COLOR[1] + ", " + COLOR[2] + ", " + BRUSH_PRESSURE + ")";
                  for (i = 0; i < scope.painters.length; i++) {
                      scope.context.beginPath();
                      var dx = scope.painters[i].dx;
                      var dy = scope.painters[i].dy;
                      scope.context.moveTo(dx, dy);
                      var dx1 = scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.mouseX) * scope.painters[i].div) * scope.painters[i].ease;
                      scope.painters[i].dx -= dx1;
                      var dx2 = scope.painters[i].dx;
                      var dy1 = scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.mouseY) * scope.painters[i].div) * scope.painters[i].ease;
                      scope.painters[i].dy -= dy1;
                      var dy2 = scope.painters[i].dy;
                      scope.context.lineTo(dx2, dy2);
                      scope.context.stroke();
                      _points.push([dx, dy, dx2, dy2]);
          
                      clear();
                  }
              }
          
              function clear(){
          
                  if(_points.length < FADESTEPS){
                      return;
                  }
          
                  var p = _points.shift();
                              if(!p){
                                  return;
                              }
                              var x = p[0],
                                  y = p[1],
                                  x2 = p[2],
                                  y2 = p[3];
                              scope.context.beginPath();
                              scope.context.lineWidth = BRUSH_SIZE + 1;
                              scope.context.moveTo(x, y);
                              scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
                              scope.context.lineTo(x2, y2);
                              scope.context.stroke();
                              scope.context.lineWidth = BRUSH_SIZE;
          
              }
          

          我知道这不是您所需要的,但我认为可以对其进行修改以获得它。

          【讨论】:

            猜你喜欢
            • 2014-01-13
            • 2013-08-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-09-16
            • 2016-01-14
            相关资源
            最近更新 更多