如何在同一个函数中使用 setInterval() 和 clearInterval()

How to use setInterval() and clearInterval() in the same function

我正在尝试创建一个启动和停止计时器的函数。启动总是在单击按钮时,但停止可能是由于计时器 运行 关闭或从另一个函数再次调用该函数。

这就是我目前所拥有的。非常适合您所看到的,但我无法弄清楚如何合并 clearInterval() 以便它在游戏获胜时停止。函数调用 timerCountdown 位于不同的 js 文件中。我已经阅读了类似问题的答案,但它们的做法似乎与我无法使其适用于我的情况的地方有所不同

我知道我需要调用 clearInterval(count) 但我不知道如何将其合并到函数本身中。

const timerCountdown = () => {                             
  let count = setInterval(() => {
    let timeLeft = timer.innerHTML
    if (timeLeft > 0) {
      timer.innerHTML = timeLeft - 1
    } else {
      gameOverMessage()
    }
  }, 1000) 
}

您可以采用全局变量 intervalId 并在获胜或没有时间可用时清除间隔计时器。

var intervalId;

const timerCountdown = () => {                             
    intervalId = setInterval(() => {
        let timeLeft = timer.innerHTML
        if (timeLeft > 0) {
            timer.innerHTML = timeLeft - 1
        } else {
            clearInterval(intervalId);
            gameOverMessage();
        }
    }, 1000) 
  },
  won = () => {
      clearInterval(intervalId);
      // additional code
  };

您需要将间隔 ID 推送到全局变量中。就像这样,您可以在需要时使用另一个函数来停止间隔。

喜欢

let intervalId; // define `count` globaly
let timer = document.getElementById('timer')

const timerStop = () => {
  clearInterval(intervalId)
}

const timerRestart = () => {
  timerStop()
  timer.innerHTML = 100;
}

const timerStart = () => {
  timerStop(); // Stop timer by security, e.g. if you call `timerStart()` multiple times
  intervalId = setInterval(() => {
    let timeLeft = timer.innerHTML;
    if (+timeLeft > 0) {
      timer.innerHTML = +timeLeft - 1
    } else {
      timerRestart();
      gameOverMessage()
    }
  }, 1000)
}
<div id="timer">100</div>

<div>
  <button onclick="timerStart()">Start</button>
  <button onclick="timerStop()">Pause</button>
  <button onclick="timerRestart()">Restart</button>
</div>

setInterval根据您指定的时间间隔,尽量space回调运行ning。问题是:在游戏中,您真正想要的是将世界的当前状态以流畅及时的方式打印到屏幕上。这与setInterval的行为不同,对屏幕一无所知,盲目重复调用。

例如:如果您在浏览器选项卡中开始 setInterval(foo, 100) 游戏,然后导航到浏览器中的另一个选项卡,请等待十秒钟,然后 return 进入您的游戏,您的 foo 回调 将被快速连续调用大约一百次 因为排队的回调是 "drained"。您不太可能想要这种行为。

requestAnimationFrame 是一个更好的解决方案,因为它仅在(不久前)您的游戏被渲染时调用 - 这正是您想要的。

在下面的代码中,createTimer 创建了一个定时器对象。计时器有 startstoptoggle 方法。

start 方法记录它何时被调用并触发 requestAnimationFrame,提供一个名为 tick 的回调。每次报价发生时,我们 运行 一些逻辑来查看要调用哪个(如果有的话)回调。

如果经过的时间大于或等于计时器的 duration,则调用 onTimeout 回调并停止计时器。

如果经过的时间小于 duration,但大于或等于 interval,那么我们更新 lastInterval 并调用 onInterval回调。

否则我们只需提示计时器的另一个滴答声。

stop方法简单地使用请求动画ID来取消定时器cancelAnimationFrame

function createTimer() {
    let rafId = null

    function start({duration = 10000, interval = 1000, onInterval, onTimeout, onStop, startTime=performance.now(), lastInterval = startTime}) {
        function tick(now=performance.now()) {
            const elapsed = now - startTime 
            if (elapsed >= duration) {
                cancelAnimationFrame(rafId)
                rafId = null
                return onTimeout()
            }

            if ((now - lastInterval) >= interval) {
                lastInterval = now
                onInterval({
                    duration,
                    elapsed
                })
            }

            rafId = requestAnimationFrame(tick)
        }
        rafId = requestAnimationFrame(tick)
    }

    function stop() {
        cancelAnimationFrame(rafId)
        rafId = null
        return onStop()
    }

    function toggle(...args) {
        rafId ? stop() : start(...args)
    }

    const timer = {
        start,
        stop,
        toggle
    }

    return timer
}

const timer = createTimer()

const onInterval = ({duration, elapsed})=>console.log(`Remaining: ${((duration - elapsed)/1000).toFixed(0)}`)
const onTimeout = ()=>console.log('Timed out.')
const onStop = ()=>console.log('Manually stopped.')

document.getElementById('btn').addEventListener('click', () => timer.toggle({
    onInterval,
    onTimeout,
    onStop
}))
<button id="btn">Toggle Timer</button>