生产环境下, 我们一般会使用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的多个。
网友评论