setTimeout,setInterval ,requestAnimationFrame定时器
定时器函数通常用于执行定时任务,也就是说你做了一个功能放在定时器函数里,它可以在特定的时间去执行你的指令,或者说隔多长时间(单位时间内—毫秒为单位)去执行。
💫在异步编程中 定时函数也是不可缺席的,常见的定时器有: setTimeout
,setInterval
,requestAnimationFrame
。
🏃setTimeout
多人认为 setTimeout
是延时多久后才开始执行函数,其实这里存在一些误解:
-
1️⃣精确延时
-
误解
:很多人认为 setTimeout 会在指定的毫秒数后精确地执行回调函数。
事实
:实际上,setTimeout 只是告诉 JavaScript 引擎在指定的时间之后尝试执行回调函数。由于 JavaScript 是单线程的,如果在这段时间内主线程上有其他任务在运行,那么回调函数的执行会被推迟到当前任务完成之后。
2️⃣ 立即执行
-
误解
:有些人认为将延迟设置为 0 毫秒(setTimeout(callback, 0))会让回调函数立即执行。
事实
:即使延迟设置为 0 毫秒,回调函数也会被推入事件队列,并在当前执行栈清空后执行。这是因为 JavaScript 的事件循环机制决定了任何 setTimeout 调用都会进入微任务队列(非立即执行的异步任务队列),并在当前宏任务完成后被执行。
3️⃣并发执行
-
误解
: 有时人们误以为多个 setTimeout 调用可以同时执行。
事实
: 虽然 setTimeout 可以并行设置多个定时器,但由于 JavaScript 的单线程特性,这些回调函数仍然会按顺序执行,不会同时运行。
4️⃣多次执行
-
误解
:一些开发者可能认为 setTimeout 会多次执行,特别是当他们试图在一个循环中设置多个定时器时。
事实
: 如果不适当地设置多个定时器,可能会导致意料之外的行为。例如,在循环中设置多个定时器而不考虑它们的执行顺序,可能导致所有回调几乎同时触发。
☕️setInterval
🎪 setInterval
函数作用和 setTimeout
基本一致,,区别在于setInterval
是每隔一段时间执行一次回调函数。
🚣 通常情况不是很推荐使用 setInterval
,原因有两点:他和 setTime
基本一致,不能保证在预期时间执行任务,其次就是存在执行积累的问题,比如说以下代码:
// 定义一个名为 demo 的函数
function demo() {
// 使用 setInterval 设置一个定时器,每隔 1000 毫秒(1秒)执行一次提供的回调函数
setInterval(function() {
console.log(2); // 每次执行时打印数字 2 到控制台
}, 1000); // 设置间隔时间为 1000 毫秒
// 尝试调用 sleep 函数,等待 2000 毫秒(2秒)
// 注意:JavaScript 中没有原生的 sleep 函数,这里是模拟休眠
sleep(2000); // 这里的 sleep 函数在标准 JavaScript 中是不存在的
// 由于 JavaScript 是单线程的,这里 sleep 函数如果是阻塞的,
// 那么它将会阻止其他所有代码(包括定时器)的执行,直到它完成
}
// 调用 demo 函数
demo();
🚅以上代码在浏览器环境中,如果定时器出现了耗时操作,由于 JavaScript 是单线程的,这里 sleep
函数如果是阻塞的,多个回调函数会在耗时操作结束以后同时执行,这样就可能会带来性能上的问题。
🎡 requestAnimationFrame
requestAnimationFrame
是一种在浏览器中用来执行动画的技术,它可以让动画更流畅且性能更高。
🚁 相比起上面两个函数它有以下优点:
-
✖️ 与屏幕刷新率同步:
-
requestAnimationFrame
的回调会在浏览器的下一帧绘制之前被调用,通常每秒 60 帧(即每 16.67 毫秒一帧),自带节流功能,这使得动画更加平滑。
➕ 自动暂停:
-
当包含动画的标签页不在前台时,
requestAnimationFrame
会自动暂停,从而节省计算资源。
➖ 更高效:
-
如果浏览器在某一帧内无法渲染(例如因为 CPU 负载过高),
requestAnimationFrame
不会堆积回调,而是跳过这一帧。
🌠requestAnimationFrame
函数的延时效果是精确的,没有其他定时器时间不准的问题,当然也可以通过该函数去实现 setTimeout
。
// 定义一个模拟 setInterval 的函数
function setInterval(callback, interval) {
// 用于存储 requestAnimationFrame 的返回值
let timer;
// 获取当前时间的函数引用,提高代码可读性和一致性
const now = Date.now;
// 初始化开始时间和结束时间
let startTime = now();
let endTime = startTime;
// 定义一个内部循环函数
const loop = () => {
// 使用 requestAnimationFrame 请求下一帧
timer = window.requestAnimationFrame(loop);
// 获取当前时间
endTime = now();
// 如果从开始到现在的时间差大于或等于指定的间隔时间 interval
if (endTime - startTime >= interval) {
// 重置开始时间和结束时间
startTime = endTime = now();
// 调用回调函数,并传入当前的 timer 值
callback(timer);
}
}
// 第一次调用 loop 函数,开始循环
timer = window.requestAnimationFrame(loop);
// 返回 timer 值,以便将来可以通过它取消动画
return timer;
}
// 初始化计数器
let a = 0;
// 使用自定义的 setInterval 函数,每隔 1000 毫秒(1秒)执行一次回调函数
setInterval(timer => {
// 打印数字 1 到控制台
console.log(1);
// 计数器增加
a++;
// 如果计数器 a 达到 3,则取消动画
if (a === 3) {
cancelAnimationFrame(timer);
}
}, 1000);
今天的分享就到这里啦,感谢大家的阅览,小江会一直与大家一起努力,文章中如有不足之处,你的支持是我前进的最大动力,请多多指教,感谢支持,持续更新中 ……