为什么使用setTimeout模拟setInterval

setInterval的缺点

  • “丢帧”现象setInterval()仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中,即在向队列插入代码实例之前,会先检测是否正在执行该代码实例,以及队列中是否已经存在该代码实例,只要存在其一就都不会再向队列中插入。

  • 实际代码执行间隔 <= 设定的时间间隔:比如一个1000ms的interval,在插入队列后因为前一个任务执行超时(假设超时800ms),导致该任务不能及时执行,当它执行完毕后(假设执行不耗时,即还剩下200ms),下一个代码(相隔200ms后)又立即添加到队列中,并立即执行,使得实际代码执行间隔(仅200ms)小于设定的间隔时间,极端条件下可能会出现代码实例连续执行的情况

  • 执行间隔变小实验代码

    // 在执行setInterval插入的第一个代码实例之前,插入一个耗时任务
    setTimeout(()=>{
        for (let i=0; i<1000000000; i++);
    }, 1000)
    
    // setInterval插入的代码,每次会打印执行的时刻
    let timer = setInterval(()=>{
        console.log(new Date().getTime())
    }, 1000)
    
    // 为了让输出简单点,清除掉间隔定时器
    setTimeout(()=>{
        clearInterval(timer)
    }, 2000)
    
    // 1615628598117
    // 1615628598315
    // 可以看到,两个代码实例执行的间隔仅 约 200ms,违背了最初 1000ms 执行间隔的初衷
    

使用setTimeout代替

  • 示例代码

    let mySetInterval = function (fn, interval, ...args) {
    	// 将需要借助的变量限制在闭包内,避免污染全局空间
        let timer = {}
        function setInt (fn, interval) {
            timer.timer = setTimeout(()=>{
                fn(...args)
                // 不断启用新的setTimeout
                setInt(fn, interval)
            }, interval)
        }
        setInt(fn, interval)
        // 必须返回引用,否则下一个setTimeout执行时,timer.timer将更新
        return timer
    }
    
    // 启动间隔定时器
    let timer = mySetInterval(()=>{
    	console.log(1)
    }, 1000)
    
    // 清除间隔定时器
    clearTimeout(timer.timer)
    
  • 解决的问题:

    1. 使用setTimeout不会有代码实例“丢帧”现象
    2. 保证了代码的执行间隔大于等于预期时间,但仍然不能保证代码可以按时执行

测试 mySetInterval

  • 实验代码
    let mySetInterval = function (fn, interval, ...args) {
    	// 将需要借助的变量限制在闭包内,避免污染全局空间
        let timer = {}
        function setInt (fn, interval) {
            timer.timer = setTimeout(()=>{
                fn(...args)
                // 不断启用新的setTimeout
                setInt(fn, interval)
            }, interval)
        }
        setInt(fn, interval)
        // 必须返回引用,否则下一个setTimeout执行时,timer.timer将更新
        return timer
    }
    
    // 在执行setInterval插入的第一个代码实例之前,插入一个耗时任务
    setTimeout(()=>{
        for (let i=0; i<1000000000; i++);
    }, 1000)
    
    // setInterval插入的代码,每次会打印执行的时刻
    let timer = mySetInterval (()=>{
        console.log(new Date().getTime())
    }, 1000)
    
    // 为了让输出简单点,清除掉间隔定时器
    setTimeout(()=>{
        clearTimeout(timer.timer)
    }, 3000)	/* 注意这里跟刚刚不一样了,因为前一个耗时大约800ms,
    mySetInterval插入的第二个代码实例的执行时刻大约为:
      1000ms(耗时操作执行的开始时间) 
    + 800ms(耗时操作的执行时间) 
    + 1000ms(mySetInterval插入的第二个代码实例的预定执行时间) == 2800ms,
    因此此处设置的清除超时定时器的预定时间需要在2800ms以上
    */
    // 1615632232151
    // 1615632233152
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tanleiDD

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值