【JS】计时器定时打印 :setTimeout()还是setInterval()?

3,140 阅读3分钟

来看一个简单的定时器实现题,要求手写一个repeact()函数,使每3秒打印一个字符串,总共执行4次。

// 手写一个repeact()函数,加上下面的代码运行,使每3秒打印一个helloword,总共执行4次
const repeatFunc = repeact(console.log,4,3000) 
repeatFunc('helloword')

一、做题之前需要知道的[1]

1、setTimeout( )和setInterval( )

setTimeout() 方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。

var timeoutID = scope.setTimeout(function[ , delay, arg1, arg2, ...]);

setInterval() 方法重复调用一个函数或执行一个代码段,在每次调用之间具有固定的时间延迟。

var intervalID = scope.setInterval(func, delay, [arg1, arg2, ...]);

2、setTimeout( )和setInterval( )的运行机制

首先,setTimeoutsetInterval属于事件循环中的宏任务,也就是说执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务队列是否为空,如果为空的话,就执行宏任务,否则就一次性执行完所有微任务。
当然这和本题关系不大,在本题中我们只需要知道,在for循环中,for循环会先执行完(同步),这时setTimeout的回调全部塞入了事件队列中,然后for循环执行完毕后一起执行了。

3、为什么要用setTimeout( )实现setInterval( )[2]

setTimeout本身就是一次调用一次执行,setTimeout 不管上次异步任务是否完成,它都会将当前异步任务推入队列,而 setInterval 则会在任务推入异步队列时判断上次异步任务是否被执行。
setTimeout :延时delay毫秒之后,直接将回调函数加入事件队列。
setInterval :延时delay毫秒之后,先看看事件队列中是否存在还没有执行的回调函数(setInterval的回调函数),如果存在,就不要再往事件队列里加入回调函数了。
setTimeout 保证调用的时间间隔是一致的,setInterval的设定的间隔时间包括了执行回调的时间。

二、代码部分

直接开始动手撸代码~
我写了三种实现方式,欢迎补充👏
1、利用For循环和setTimeout()来实现
2、setTimeout()嵌套实现
3、setInterval()和 setTimeout()结合实现

1、利用For循环和setTimeout()来实现

for循环中用setTimeout()应该是最经典也是最容易的解法了。
实现的关键点在于,for循环一次碰到一个 setTimeout(),并不是马上把setTimeout()拿到异步队列中,而要等到delay时间到了后,才将其放到任务队列里面。for循环结束,这时执行到期的回调函数。

function repeact(func, count, time) {
  return function (s) {
    //执行循环count次,for循环会先执行完,将所有settimeout压入执行栈
    //delay时间到了后 setTimeout的回调函数被放到任务队列,执行
    for (let i = 0; i < count; i++) {
      setTimeout(() => {
        func(s)
      }, time * (i + 1));
    }
  }
}
const repeatFunc = repeact(console.log, 4, 3000);
repeatFunc("helloword");

2、setTimeout()嵌套实现

设置两个setTimeout(),一个setTimeout()调用interval函数,interval函数包裹另一个setTimeout()interval函数递归调用自己,每调用一次,count--,终止条件为count变量为0。

function repeact(func, count, time) {
  return function (word) {
    function interval(func, time) {
      let interv = function () {
        func(word)
        count--
        let timer = setTimeout(interv, time);
        if (timer && count <= 0) {
          clearTimeout(timer);
          timer = null;
        }
      }
      setTimeout(interv, time)
      }
    interval(func, time)
  }
}
const repeatFunc = repeact(console.log, 4, 3000)
repeatFunc('helloword')

3、setInterval()和 setTimeout()结合实现

关键在于设置endTime,endTime = time * (count + 1),到期后setTimeout()setInterval()清除。

function repeact(func, count, time) {
  return function excu(words) {
    let timer = setInterval(() => {
      func(words)
    }, time);
    let endTime = time * (count + 1)
    setTimeout(() => { clearInterval(timer) }, endTime);
  }
}
const repeatFunc = repeact(console.log, 4, 3000)
repeatFunc('helloword')

结语

到这里就结束啦,欢迎在评论区留言讨论👏欢迎指出问题

参考文章:

1 2