定时器在前端的编程中 非常普遍, 最经常看到的是 抢购倒计时, 时钟 , 定时循环,。。。
但是浏览器的多线程模式 && js的单线程执行逻辑, 注定setTimeout、 setInterval 是非准确的 。
举例 一个完善的定时任务逻辑 是这样, Js 代码执行到 setTimeout 异步任务 ,会把异步任务推送到定时器线程, 定时器线程开始倒计时, 当倒计时结束,会把异步任务中的 回调函数 ,压入到js 的宏任务队列中, 此时 如果js 线程的执行栈中有大量的 耗时任务,显然会延迟事件循环 从宏任务队列中 取出回调函数到执行栈中执行.
那如何实现一个准确的定时器?
HTML5 给出一个新的API window.requestAnimationFrame 其本身 用来更好的渲染动画效果 ,window.requestAnimationFrame()
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行 -------MDN
但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配,通常是60次每秒。
下面可以实现我们自己的 setTimeout
function mySetTimeOut(callBack , interval) {
let timer={value:undefined} ;
const now = Date.now ;
let startTime = now();
let endTime = startTime ;
const loop = () => {
timer.value = window.requestAnimationFrame(loop);
endTime = now () ;
if (endTime-startTime >= interval){
endTime = startTime = now();
cancelAnimationFrame(timer.timer);
callBack&&callBack(timer);
}
}
timer.value = window.requestAnimationFrame(loop);
console.log('timer',timer);
return timer ;
}
function clearMySettimeout (timer){
timer&&timer.value &&cancelAnimationFrame(timer.value) ;
}
const timer= mySetTimeOut(()=>{},1000);
如上我们也可以 模拟setInterval
function mySetInterval(callBack , interval) {
let timer={value:undefined} ;
const now = Date.now ;
let startTime = now();
let endTime = startTime ;
const loop = () => {
timer.value = window.requestAnimationFrame(loop);
endTime = now () ;
if (endTime-startTime >= interval){
endTime = startTime = now();
callBack&&callBack();
}
}
timer.value = window.requestAnimationFrame(loop);
return timer ;
}
function clearMysetInterval (timer){
timer&&timer.value &&cancelAnimationFrame(timer.value) ;
}
const timer= mySetInterval(()=>{},1000);