跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

JavaScript 事件循环进阶:rAF、Web Workers 与任务调度

综述由AI生成本文深入探讨了 JavaScript 事件循环的高级应用场景。首先对比了 setInterval、setTimeout 与 requestAnimationFrame 在动画实现上的差异,强调了 rAF 在同步屏幕刷新率和资源管理上的优势。接着介绍了 Web Workers 如何实现真正的多线程编程,解决了 CPU 密集型任务阻塞主线程的问题,并列举了 Dedicated、Shared、Service 等多种 Worker 类型及其限制。最后讲解了 requestIdleCallback 如何在浏览器空闲期调度低优先级任务,配合 deadline 机制优化页面性能。文章通过代码示例和事件循环时序分析,帮助开发者构建更流畅、响应更快的 Web 应用。

深海蔚蓝发布于 2026/4/9更新于 2026/5/2220 浏览

前言:从 60fps 的动画说起

想要实现丝滑流畅的 60fps 动画,或者在单线程 JavaScript 中实现真正的并行计算,关键在于理解事件循环的高阶应用。在 JavaScript 中,常见的动画实现方式主要有三种,它们的性能表现差异很大。

使用 setInterval(不推荐)

function animateWithSetInterval() {
    setInterval(() => {
        updateAnimation();
        renderFrame();
    }, 16.67);
}

上述代码试图达到 60fps(1000/60 ≈ 16.67ms),但定时器并不精确,容易导致丢帧或过度绘制,资源浪费严重。

递归 setTimeout

function animateWithSetTimeout() {
    function loop() {
        updateAnimation();
        renderFrame();
        setTimeout(loop, 16.67);
    }
    loop();
}

这种方式比 setInterval 稍好,因为它允许在每次循环前重置计时器,但仍可能和屏幕刷新不同步,造成画面撕裂。

使用 requestAnimationFrame(推荐)

function animateWithRAF() {
    function loop(timestamp) {
        updateAnimation(timestamp);
        renderFrame();
        requestAnimationFrame(loop);
    }
    requestAnimationFrame(loop);
}

这是目前浏览器推荐的方案。优势在于它能自动匹配屏幕刷新率,节省资源,避免不必要的渲染。

requestAnimationFrame:动画的黄金标准

什么是 requestAnimationFrame?

requestAnimationFrame(简称 rAF)是浏览器专门为动画和连续视觉更新提供的 API。它的核心特点是:在浏览器下一次重绘之前调用指定的回调函数,确保动画与屏幕刷新同步。

rAF 的基本用法

function animate() {
    // 更新动画状态
    updateAnimation();
    // 渲染当前帧
    renderFrame();
    // 请求下一帧
    requestAnimationFrame(animate);
}
// 启动动画循环
requestAnimationFrame(animate);

rAF 的优势

  1. 自动匹配显示器刷新率(通常是 60Hz)
  2. 页面不可见时自动暂停,节省资源
  3. 浏览器可以优化动画性能
  4. 提供精确的时间戳参数

rAF 的工作原理

function experimentRAF() {
    console.log('实验开始');
    let frameCount = 0;
    let lastTimestamp = 0;
    
    function frameCallback(timestamp) {
        frameCount++;
        if (lastTimestamp > 0) {
            const interval = timestamp - lastTimestamp;
            console.log(`第${frameCount}帧,间隔:${interval.toFixed(2)}ms`);
        }
        lastTimestamp = timestamp;
        if (frameCount < 10) {
            requestAnimationFrame(frameCallback);
        } else {
            console.log('实验结束,平均帧率:', (1000 / ((timestamp - startTime) / 10)).toFixed(1), 'fps');
        }
    }
    const startTime = performance.now();
    requestAnimationFrame(frameCallback);
}

这里的关键点在于 frameCallback() 回调中的 timestamp 参数。这个时间值是 performance.now() 返回的高精度时间,表示回调开始执行的时刻,非常适合用来计算帧间隔。

rAF 在事件循环中的位置

理解 rAF 的执行时机对于调试至关重要。我们可以通过以下代码观察它在事件循环中的确切位置:

setTimeout(() => {
    console.log('1. setTimeout - 宏任务');
    Promise.resolve().then(() => {
        console.log('2. setTimeout 中的微任务');
    });
}, 0);

Promise.resolve().then(() => {
    console.log('3. Promise - 微任务');
    requestAnimationFrame(() => {
        console.log('4. Promise 中注册的 rAF');
    });
});

requestAnimationFrame(() => {
    console.log('5. 直接注册的 rAF');
    setTimeout(() => {
        console.log('6. rAF 中注册的 setTimeout');
    }, 0);
});

queueMicrotask(() => {
    console.log('7. queueMicrotask - 微任务');
});

console.log('8. 同步代码');

输出顺序如下:

  • 8. 同步代码
    1. Promise - 微任务
    1. queueMicrotask - 微任务
    1. setTimeout - 宏任务
    1. setTimeout 中的微任务
    1. 直接注册的 rAF
    1. Promise 中注册的 rAF
    1. rAF 中注册的 setTimeout

其执行过程大致为:执行宏任务 -> 执行微任务 -> 执行 rAF 回调 -> 样式计算和布局 -> 绘制 -> 合成 -> 检查空闲。

Web Workers:真正的多线程编程

什么是 Web Workers?

Web Workers 允许 JavaScript 在后台线程中运行脚本,而不会阻塞主线程。这意味着我们可以执行 CPU 密集型任务,而不会影响页面的响应性。

// 主线程代码
console.log('主线程:开始');
const worker = new Worker('worker.js');

// 向 Worker 发送消息
worker.postMessage({ type: 'CALCULATE', data: { numbers: [1, 2, 3, 4, 5] } });

// 接收 Worker 的消息
worker.onmessage = (event) => {
    const result = event.data;
    console.log('主线程:收到 Worker 结果', result);
    document.getElementById('result').textContent = `结果:${result}`;
};

// 处理 Worker 错误
worker.onerror = (error) => {
    console.error('Worker 错误:', error);
};

console.log('主线程:继续执行其他任务...');

Worker 的限制

虽然功能强大,但 Worker 也有严格的限制:

  1. 无法访问 DOM
  2. 无法使用 window、document 等全局对象
  3. 不能执行同步的 XHR(可以使用 fetch)
  4. 有同源策略限制
  5. 不能加载本地文件(file://协议)

Web Workers 的类型

1. 专用 Worker (Dedicated Worker)

只能被创建它的脚本使用:

const dedicatedWorker = new Worker('dedicated-worker.js');
2. 共享 Worker (Shared Worker)

可以被多个脚本共享(同源):

if (window.SharedWorker) {
    const sharedWorker = new SharedWorker('shared-worker.js');
    sharedWorker.port.onmessage = (event) => {
        console.log('收到共享 Worker 消息:', event.data);
    };
    sharedWorker.port.postMessage('Hello Shared Worker');
} else {
    console.log('浏览器不支持 Shared Worker');
}
3. Service Worker

用于离线缓存、推送通知等:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('service-worker.js')
        .then(registration => {
            console.log('Service Worker 注册成功:', registration);
        })
        .catch(error => {
            console.error('Service Worker 注册失败:', error);
        });
}
4. Audio Worklet (Chrome 66+)

用于高性能音频处理:

if (window.audioContext && window.audioContext.audioWorklet) {
    audioContext.audioWorklet.addModule('audio-processor.js')
        .then(() => {
            console.log('Audio Worklet 加载成功');
        });
}
5. Paint Worklet (CSS Houdini)

用于自定义 CSS 绘制:

if (CSS.paintWorklet) {
    CSS.paintWorklet.addModule('paint-worklet.js')
        .then(() => {
            console.log('Paint Worklet 加载成功');
        });
}

requestIdleCallback:空闲期任务调度

什么是 requestIdleCallback?

requestIdleCallback(简称 rIC)允许开发者在浏览器空闲时期调度任务。这对于执行低优先级或非紧急的工作非常有用,避免影响关键的用户交互和动画。

const idleCallbackId = requestIdleCallback((deadline) => {
    console.log('空闲回调开始执行');
    console.log('剩余时间:', deadline.timeRemaining(), 'ms');
    console.log('是否超时:', deadline.didTimeout);

    while (deadline.timeRemaining() > 0 && hasMoreWork()) {
        doSomeLowPriorityWork();
    }

    if (hasMoreWork()) {
        requestIdleCallback(processLowPriorityWork);
    }
    console.log('空闲回调结束');
}, { timeout: 1000 });

console.log('主线程继续执行...');

rIC 的关键特点

  1. 只在浏览器空闲时执行
  2. 提供 deadline 对象,包含剩余时间信息
  3. 可以设置 timeout 确保执行
  4. 适合低优先级、可中断的任务

rIC 在事件循环中的位置

console.log('=== 事件循环中各 API 的执行时机 ===');
setTimeout(() => {
    console.log('1. setTimeout - 宏任务');
}, 0);

Promise.resolve().then(() => {
    console.log('2. Promise - 微任务');
});

requestAnimationFrame(() => {
    console.log('3. requestAnimationFrame - 动画帧回调');
    requestIdleCallback(() => {
        console.log('5. rAF 中安排的 rIC - 空闲回调');
    }, { timeout: 100 });
});

requestIdleCallback(() => {
    console.log('4. 直接安排的 rIC - 空闲回调');
    Promise.resolve().then(() => {
        console.log('6. rIC 中的 Promise - 微任务');
    });
}, { timeout: 100 });

queueMicrotask(() => {
    console.log('7. queueMicrotask - 微任务');
});

console.log('8. 同步代码');

输出顺序:

  • 8. 同步代码
    1. Promise - 微任务
    1. queueMicrotask - 微任务
    1. setTimeout - 宏任务
    1. requestAnimationFrame - 动画帧回调
    1. 直接安排的 rIC - 空闲回调
    1. rIC 中的 Promise - 微任务
    1. rAF 中安排的 rIC - 空闲回调

执行流程总结:宏任务 -> 微任务 -> rAF 回调 -> 样式计算 -> 绘制 -> 合成 -> 检查空闲时间执行 rIC。

核心概念总结

requestAnimationFrame (rAF)

  • 是什么:浏览器提供的动画 API,在每次重绘前执行回调
  • 为什么用:自动匹配显示器刷新率,页面不可见时暂停,节省资源
  • 最佳时机:视觉更新、动画、连续状态变化
  • 执行位置:在微任务之后,重绘之前

Web Workers

  • 是什么:允许 JavaScript 在后台线程运行的技术
  • 为什么用:执行 CPU 密集型任务而不阻塞主线程
  • 限制:无法访问 DOM,通过消息传递通信
  • 类型:专用 Worker、共享 Worker、Service Worker 等

requestIdleCallback (rIC)

  • 是什么:在浏览器空闲时调度任务的 API
  • 为什么用:执行低优先级、非紧急任务
  • 关键对象:deadline 包含剩余时间和超时信息
  • 执行位置:在一帧的最后,如果有空闲时间

目录

  1. 前言:从 60fps 的动画说起
  2. 使用 setInterval(不推荐)
  3. 递归 setTimeout
  4. 使用 requestAnimationFrame(推荐)
  5. requestAnimationFrame:动画的黄金标准
  6. 什么是 requestAnimationFrame?
  7. rAF 的基本用法
  8. rAF 的优势
  9. rAF 的工作原理
  10. rAF 在事件循环中的位置
  11. Web Workers:真正的多线程编程
  12. 什么是 Web Workers?
  13. Worker 的限制
  14. Web Workers 的类型
  15. 1. 专用 Worker (Dedicated Worker)
  16. 2. 共享 Worker (Shared Worker)
  17. 3. Service Worker
  18. 4. Audio Worklet (Chrome 66+)
  19. 5. Paint Worklet (CSS Houdini)
  20. requestIdleCallback:空闲期任务调度
  21. 什么是 requestIdleCallback?
  22. rIC 的关键特点
  23. rIC 在事件循环中的位置
  24. 核心概念总结
  25. requestAnimationFrame (rAF)
  26. Web Workers
  27. requestIdleCallback (rIC)
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 鸿蒙电商购物车全栈项目:订单管理、支付管理与 AI 原生功能实现
  • Python 环境配置与 pip 安装教程
  • IntelliJ IDEA 常用快捷键指南
  • AI 大模型 API 中转平台选择与接入指南
  • 基于 ESP32-S3 的智能家居键盘 SmartKB32_v2 双模控制器设计
  • TRAE 平台 MCP Server 使用指南:常见问题与解决方案
  • ModelScope 魔搭社区介绍与大模型微调指南
  • 使用 Claude Code 与 GLM4.7 修复前端 Bug 的踩坑实录与反思
  • 阿里云部署 OpenClaw 搭建 24 小时 AI 代理
  • 2026 年主流 AI 大模型实测排名与选型指南
  • llama.cpp Vulkan 后端编译难题解决:环境配置与实战修复
  • AgentScope Java 与 Spring AI Alibaba Workflow 集成指南
  • Java 线程池 ThreadPoolExecutor 入门:原理、核心参数与图解
  • Spring MVC 快速入门:Web 响应处理
  • Cloudflare Turnstile 在 Java 后端的人机验证实践
  • 如何将本地已有项目关联并推送到指定的远程仓库
  • 使用 HTML 和 JavaScript 实现滑动验证码
  • Spring 配置文件详解:Properties 与 YAML 格式对比及实战
  • Dubbo 服务降级:Mock 机制原理与实战应用
  • Java 驱动的无人共享宠物洗澡物联网系统架构

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online