定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行
每个setTimeout产生的任务会直接push到任务队列中;而setInterval在每次把任务push到任务队列前,都要进行一下判断看上次的任务是否仍在队列中,如果没有了,才会将这次的定时器任务添加到事件队列
setInterval的缺点:
1、使用setInterval时,某些间隔会被跳过
2、多个定时器内的函数可能会连续执行
举例说明:
setInterval每隔100ms往队列中添加一个事件;100ms后,添加P1定时器的代码至队列中,主线程中还有其它任务在执行,所以等待,其它任务执行结束后执行P1定时器代码;又过了100ms,P2定时器被添加到队列中,主线程还在执行P1代码,所以等待;又过了100ms,理论上又要往队列里推一个定时器代码,但由于此时P2还在队列中,所以P3不会被添加,结果就是此时被跳过;然后可以看到,P1定时器执行结束后马上执行了P2代码,所以并没有达到定时器的效果。
setTimeout 模拟 setInterval
function fn(){ // 定时器任务逻辑... setTimeout(fn,1000); } //fn() setTimeout(fn,1000);
好处:
1、etTimeout
能够保证只有前一个setTimeout任务
执行完毕,才会创建一个新的setTimeout任务(解决了setInterval缺点一)
2、也能保证setTimeout任务
执行的时间间隔至少为指定的时间间隔(解决了setInterval缺点二)
把定时器停下来的方法:
var count = 0; function fn(){ console.log(count++); if(count === 10){ fn = null; } setTimeout(fn,1000); } setTimeout(fn,1000);
使用定时器实现动画效果的缺点?
浏览器每秒刷新60次,相当于1000ms / 60 = 16.666ms刷新一次, 如果定时器能保证 每隔 16ms
就一定会调用一次回调函数,那么动画效果就会很好,但是定时器做不到。
定时器作为作为浏览器的异步线程之一,它会不停的计算时间,但每过16ms,回调函数会不会被执行,它做不了主,因为所有的同步任务都是在js引擎线程上执行。
如果同步任务太多,js引擎线程忙不过来,那么定时器回调函数就会一直在任务队列里待着,,时间慢慢过去,动画也就卡起了。
也就是说,定时器无法保证回调函数按照指定的时间间隔被调用从而导致动画卡顿
解决方案:使用 requestAnimationFrame
equestAnimationFrame(callback)
会请求浏览器在 下次刷新前 调用回调函数,所以requestAnimationFrame
里的回调函数 每秒会被调用60次
requestAnimationFrame实现动画例子
start.onclick = function(){ function move(timeStamp){ // 动画逻辑 requestAnimationFrame(move); } requestAnimationFrame(move); }
requestAnimationFrame
来实现动画,效果更理想,cancelAnimationFrame 方法用于取消
定时器实现防抖
const slice = Array.prototype.slice; //函数防抖 function debounce(fn,context,delay=100){ var timer = null; var args = slice.call(arguments,3); return function(){ if(timer!==null) clearTimeout(timer); var finalArgs = args.concat(slice.call(arguments)); timer = setTimeout(() => { fn.apply(context,finalArgs); },delay); } }
改变浏览器窗口大小、最大化或最小化浏览器窗口都会触发resize事件
。如果resize事件
的事件程序中包含了大量的DOM操作,将占用较多的内存,可能导致浏览器崩溃。这时,函数防抖就派上用场
定时器实现函数节流
const slice = Array.prototype.slice; //函数节流 function throttle(fn,context,delay=100){ var timer = null; var args = slice.call(arguments,3); return function(){ if(timer!=null) return; var finalArgs = args.concat(slice.call(arguments)); timer = setTimeout(() => { fn.apply(context,finalArgs); timer = null; },delay); } }
函数节流的典型应用就是scroll事件
了。连续触发scroll事件
,保证单位时间内才会触发一次
再比如滑动滚动条加载图片,想要实现在一定时间后就加载新的图片,而不是一定要等到停止滑动鼠标才加载图片