«

setTimeout代替setInterval稳定循环延时

时间:2023-6-6 17:11     作者:六度科技     分类: JS


生产环境下, 我们一般会使用setTimeout代替setInterval,为啥呢?
setInterval其实就是每隔一段时间,往任务队列推入一个回调函数。假如setInterval的回调需要进行大量的dom操作,这样一来,每次任务花的时间就比较长,由于设定了间隔时间,有可能前一次代码还没有执行完,后一次代码就被添加到任务队列了。随着等待的任务不断增多,这样一旦JS引擎空闲后一个任务会马上得到执行,这样慢慢就变成了连续执行回调,设置的时间间隙就失去了作用。如何解决呢?
我们可以用setTimeout递归调用的思路来代替setInterval。

function mysetInterval(){
    let fn = null
    let isClear = arguments[0]//是否清除定时器
    let callee = arguments.callee //函数自身引用
    if(typeof isClear === 'boolean'){

        fn = arguments[1]
        fn.timer && clearTimeout(fn.timer)//清除定时器,回调的callee不会再执行

    } else {
        fn = arguments[0]  //回调函数
        let wait = arguments[1]  //间隔时间
        let that = this

        fn.timer = setTimeout(function(){

            fn.call(that)
            callee(fn,wait)//回调函数中递归调用自身
        },wait)
    }

}
//测试一下
function foo(){
    console.log(new Date())
}
mysetInterval(foo,1000)//1s执行一次

//5s后停止
setTimeout(function(){
    mysetInterval(true,foo)
},5000)

问题的关键,就在setTimeout的回调函数中callee调用自身。
我们推演一下执行顺序。
当mysetInterval函数执行后,代码走到else并建立一个定时器,此时定时器线程拿到了timeout开始执行,经过1s后,将回调函数推入宏任务队列,定时器线程空闲,目前宏任务队列只有一个回调函数待执行。
等JS引擎空闲后,执行栈从宏任务队列拿到这一个回调函数并开始执行,执行完毕后,再次建立一个timeout。定时器再次拿到timeout开始执行,1s后将回调函数推入宏任务队列,以此类推。。。。
这样做的好处是可以保证每次消息队列只有一个回调函数待执行,而不是setInterval的多个。

作者:姜治宇
链接:https://www.jianshu.com/p/2ccf86e07824
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签: 网页 js