requestAnimationFrame何时触发?为何需等JS代码执行完才生效?
为什么requestAnimationFrame没有按预期执行?
嘿,这个问题其实戳中了JavaScript单线程模型的核心点——这也是很多刚接触requestAnimationFrame的开发者容易踩的坑!
首先得明确一个关键事实:JavaScript是单线程执行的,而requestAnimationFrame的回调函数并不是立即执行的——它会被浏览器加入到「下一次重绘前的回调队列」里,但只有当主线程完全空闲时,这个队列里的任务才会被执行。
咱们来拆解你这段代码的执行流程,就能明白为什么看不到倒计时效果:
- 初始化变量
n=0,定义sleep和refresh函数 - 重置
n=0,调用window.requestAnimationFrame(refresh)——这一步只是把refresh函数“预约”到浏览器的下一次重绘时机,但此时它并没有执行 - 执行
n = n+1,n变成1;接着调用sleep(2000)——这个sleep是用同步循环实现的,会死死占用主线程2秒钟,期间浏览器根本无法处理任何回调(包括requestAnimationFrame的回调),页面也不会有任何更新 - 2秒后,
n变成2,再次调用sleep(2000)——主线程又被阻塞2秒,同样无法处理回调 - 又过2秒,
n变成3,整个脚本终于执行完毕,主线程空闲了!这时候浏览器才会执行之前预约的refresh函数,把body的文本设置为3——所以你最终只能看到这个结果
怎么实现预期的倒计时效果?
要避免同步阻塞主线程,得用异步的方式来实现延迟。这里给你两种可行的方案:
方案1:用requestAnimationFrame递归实现倒计时
let n = 3; function refresh() { const body = document.body; body.textContent = n; n--; if (n >= 0) { // 用setTimeout控制1秒间隔,requestAnimationFrame保证重绘时机 setTimeout(() => window.requestAnimationFrame(refresh), 1000); } } // 启动倒计时 window.requestAnimationFrame(refresh);
方案2:用async/await模拟异步延迟
async function sleep(ms) { // 用Promise实现真正的异步延迟,不会阻塞主线程 await new Promise(resolve => setTimeout(resolve, ms)); } async function startCountdown() { let n = 0; const body = document.body; function refresh() { body.textContent = n; } while (n <= 3) { window.requestAnimationFrame(refresh); n++; await sleep(2000); } } startCountdown();
这两种方式都不会阻塞主线程,requestAnimationFrame的回调能在每次延迟后及时执行,从而实现你想要的倒计时效果。
内容的提问来源于stack exchange,提问作者Phillipp Schwarz
相关产品推荐
相关产品推荐

