如果可能的话,我是否应该永远不使用setInterval和setTimeout?

15

我正在学习用JavaScript编程。我正在编写一些带有定时鼠标动画的程序。我即将添加一些代码,以绘制鼠标路径。

这将是一个接收mousemove事件的东西,每次鼠标移动时在Canvas上绘制新的路径线。随着时间的推移,该路径将变得更加透明,直到消失。当然,新路径总是不透明的,因此存在连续的运动。

我想出了一种方法,可以只使用requestanimationframe来完成这项工作。基本上,每当发生新的mousemove事件时,我将该鼠标路径坐标添加到名为mousePathArray的对象数组中。该对象将携带路径坐标和“animationStage”计数器。该计数器基本上将确定在“动画”的那个阶段路径有多透明。(后期阶段将意味着更透明。)

然后,每个动画帧都会调用一个函数,该函数将遍历数组中的所有对象,并根据它们的坐标和animationStage计数器绘制线条,将计数器增加1,并在animationStage计数器达到结束数字(可能为50或其他数字)时删除数组对象。

虽然可以做到这一切,但听起来与所有这些相比,只需引入一个setInterval函数,每次鼠标移动时都会被调用,会更容易些。

那么这样做值得吗?通过不使用setInterval和rAF,在JS中进行良好的实践,这可能会更快一些或更好一些?

在写下所有这些内容之后,我实际上编写了我上面谈论过的仅使用rAF的代码。它太长了无法粘贴到这里,但规则需要它。在jsfiddle上是这样的:http://jsfiddle.net/06f7zefn/2/

(我知道存在很多低效率和可能可怕的编码实践,但请容忍我,我才5天学习这个!与其在每个帧上调用animate(),我可以创建一个isDrawing?布尔值,以及只需进行一次ctx.moveTo(),其余均为LineTo(),而无需在每次迭代中都调用moveTo(),因为一个点从另一个点开始。)

如果我能传达我所谈论的主要思想,那就是我在询问您的意见的地方。在这里,是否最好使用setInterval或setTimeout替代将与时间相关的所有内容起源于rAF调用?

var canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);

var ctx = canvas.getContext('2d');

var currentPosX, currentPosY, prevPosX, prevPosY;
var mousePathArray = [];

canvas.addEventListener ('mousemove', mouseOp);


function mouseOp (mouseEvent) {
    prevPosX = currentPosX;
    prevPosY = currentPosY;
    currentPosX = mouseEvent.clientX;
    currentPosY = mouseEvent.clientY;
    mousePathArray.push( {
        x1: currentPosX,
        x2: prevPosX,
        y1: currentPosY,
        y2: prevPosY,
        animStage: 0
    });
}

function animate () {
    var anims = mousePathArray.length;
    if (anims!=0) {
        for (i=0; i<anims; i++) {
            if (mousePathArray[i].animStage == 20) {
                mousePathArray.splice(i, 1);
                i--;
                anims--;
                continue;
            }

            drawLine(mousePathArray[i].x1, mousePathArray[i].x2, mousePathArray[i].y1, mousePathArray[i].y2, 1 - (mousePathArray[i].animStage * 0.05));
            mousePathArray[i].animStage ++;
        }
    }
}

function drawLine (x1, x2, y1, y2, alpha) {
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.strokeStyle = "rgba(150, 20, 150," + alpha +")";
    ctx.stroke();
}

animloop();

function animloop(){
  window.requestAnimationFrame(animloop);
  gameLoop();
}

function gameLoop() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    animate();
}

定义“更好”。更健壮?更大的支持?更简单的维护?更大的库依赖? - RobG
好问题。我不确定如何准确地定义更好的代码,因为我是新手,但我试图从更可靠的代码角度来看待它,这样就不容易出现错误,而且可能更快。 - irenicus
问问自己这个问题:5fps和120fps的动画看起来一样吗?如果你的答案是否定的,那么你的算法选择了错误的方法。 - Bergi
我的动画时序取决于上一帧的调用。因此,如果我有一个5帧的动画,在5fps下它需要1秒钟才能播放一遍,而在60fps以下则需要不到0.1秒钟。 - irenicus
3个回答

24

我不认为使用setIntervalsetTimeout是一种不好的做法。当你想在将来某个时间执行某些操作,但是你不确定何时可以进行操作时,使用setTimeout就是不好的做法。例如:

makeHeavyDomMovements();
setTimeout(function () {
  //with 3000 timeout I'm sure any device has made my changes
  makeNextMove();
}, 3000);

正确的方式是:

makeHeavyDomMovements().
then(function () {
   makeNextMove();
});

如果你想在未来的某个时候像延迟100毫秒后响应用户操作一样做某事,最佳实践是使用 setTimeout;或者如果你想将某些东西放入浏览器队列中,则应使用 setTimeout(或者根据需要使用工作者)。

setInterval 的使用方式与此类似。如果你正在使用它来定期执行某项任务,那么你就使用得正确,这不是一个坏习惯。但是下面是 setInterval 的错误用法:

var dbInterval = setInterval(function () {
  if (dbIsReady()) {
    clearInterval(dbInterval);
    fireReadyEvent();
  }
}, 300);

这里是一个关于 setInterval 的常规用法:

setInterval(function () {
  runSync();
}, 600000);

不良做法和良好做法是由你使用环境工具的方式定义的,而不是工具本身。


1
你是不是想说“future”而不是“feature”?(两次) - Bergi
1
谢谢,我纠正了许多拼写错误,我认为你想再看一下。 - Iman Mohamadi
使用setTimeout作为调度程序需要注意,因为有些浏览器会在设备进入睡眠状态时暂停计时器(我看到你了,iOS Safari!)例如,如果你希望在特定的时间做某件事情,那么这个超时时间将会受到用户设备自创建以来进入睡眠状态的时间影响。 - Ken Gregory

3

Luz Caballero认为,rAF是setInterval的有用替代品,其原因如下:

https://dev.opera.com/articles/better-performance-with-requestanimationframe/.

我现在使用rAF而不是setInterval,因为rAF具有一些内置的便利性,而使用setInterval需要额外的编码:

  • rAF将尝试与显示刷新周期同步调用。这使循环中的代码有"最佳机会"在刷新周期之间完成。

  • 如果循环#1中的任务不能在请求循环#2之前完成,则不会调用新的循环#2,直到未完成的循环#1完成。这意味着浏览器不会被积累的循环队列拖垮。

  • 如果用户切换到另一个浏览器选项卡,则当前选项卡中的循环将被暂停。这允许处理能力转移到新选项卡上,而不是在当前不可见的循环标签中使用。这也是电池供电设备的省电特色。如果您希望循环在选项卡失焦时继续处理,则必须使用setInterval。

  • rAF回调函数自动获取高精度时间戳参数。这允许rAF循环计算和使用经过的时间。此经过的时间可用于(1)延迟直至发生指定的经过时间或(2)"赶上"无法完成但需要完成的基于时间的任务。


0

如果你百分之百确定需要它,那么可以使用它们。如果情况不明确,则不要使用它们。如果你正在使用JavaScript框架,请查找生命周期钩子,例如在Angular中使用Angular生命周期钩子。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接