JavaScript 多线程:Web Worker 其实很简单

JavaScript 在诞生之初主要用于浏览器 DOM 操作与用户交互。设想这样一个场景:多个线程同时操作同一个 DOM 节点,一个线程执行删除、另一个线程执行修改,浏览器将无法确定最终应该采用哪一份结果。为了规避 DOM 并发竞争冲突,JavaScript 主线程被设计为单线程模型,所有业务代码都在主线程按顺序串行执行。

JavaScript 语言本身是单线程,但浏览器、Node.js 运行环境提供了多线程能力:

  • 浏览器:Web Worker,在浏览器内部开辟独立后台线程;
  • Node.js:Worker Threads,服务端实现并行计算。

Chrome 浏览器进程与线程模型

Chrome 采用多进程隔离架构,不同职责运行在独立进程,单个进程崩溃不会造成整个浏览器闪退。

浏览器主进程

浏览器顶层总控进程,不负责网页渲染,管理全部标签页整体行为。

  • UI 线程:绘制浏览器本身界面,地址栏、书签、前进后退按钮,响应用户点击输入;
  • 存储线程:读写 localStorage、IndexedDB、Cookie、文件系统;
  • IPC 进程通信线程:负责浏览器各个进程之间消息分发;
  • 文件 IO 线程:处理本地文件读写操作。

渲染进程(Renderer 进程)

每一个网页 Tab 或者同源 iframe,一般分配独立渲染进程。网页解析、JS 执行、页面绘制、用户交互全部在此完成,页面崩溃只会影响当前标签页。

内部子线程:

  • 主线程:JavaScript 执行核心线程,解析 HTML、CSS 生成 DOM 树、CSSOM 树,样式计算、重排 Layout、绘制 Paint;处理 DOM 事件,运行事件循环,调度宏、微任务。
  • 合成线程:专门处理滚动、CSS 动画,将多个图层合并为屏幕最终图像。
  • 光栅化线程池:把图层矢量绘图指令转换为像素位图。
  • Worker 线程:运行各类 Worker(Dedicated、Shared、Service Worker),完全无法访问 DOM,依靠消息机制和主线程通信。
  • 音频线程:Web Audio 音频解码、混音输出。
  • 视频解码线程:软硬件解码视频流。

GPU 进程

专门处理图形加速,最初为 CSS3D 效果设计,现在网页 UI 渲染也大量依赖 GPU。

  • GPU 主线程:调用系统图形驱动,管理显存、纹理、着色器资源;
  • 合成线程:处理 CSS3D 变换、WebGL 图层合成;
  • 显示线程:将合成完成帧输出到显示器。

网络进程

统一接管全部网络请求,独立于渲染进程。

  • 网络调度线程:管理请求队列、优先级、TCP 连接复用;
  • HTTP/2 线程:处理 HTTP/2 多路复用、头部 HPACK 压缩;
  • QUIC/HTTP/3 线程:UDP 协议,解决 TCP 队头阻塞;
  • 缓存线程:读写 HTTP 缓存,校验 Cache-Control、ETag。
  • 代理线程:处理各类 HTTP、SOCKS 代理转发。

插件进程

用于运行 NPAPI、PPAPI 老式插件(Flash、旧 PDF 阅读器),现代 Web 已经极少使用。

JavaScript 主线程

JS 所有业务代码运行在渲染进程的主线程,语言层面单线程。

主线程核心特点

  • 同一时间只能执行一件任务;
  • 同步任务顺序执行;
  • 异步依靠事件循环 Event Loop调度,并非真正并行。

主线程承担工作

  • JS 代码执行:执行同步代码,消费宏任务、微任务队列;
  • 网页解析构建:解析 HTML、CSS,生成 DOM 树、CSSOM 树;
  • 样式计算:计算每个 DOM 元素最终生效样式;
  • 布局(重排):计算元素坐标、宽高尺寸;
  • 绘制记录:把布局结果转为绘图指令;
  • 事件系统:捕获、冒泡用户交互事件,执行事件回调;
  • 事件循环调度:维护宏任务、微任务队列,驱动异步执行;
  • 跨线程协作:和合成线程、Worker 线程完成消息交互。

主线程页面完整执行流程

  1. 用户输入 URL,网络进程下载 HTML 资源;
  2. 主线程接收 HTML 二进制数据,开始解析构建 DOM;
  3. 遇到 <script> 标签,暂停 HTML 解析,加载并执行 JS,JS 可以修改 DOM 与 CSSOM;
  4. JS 执行完毕,恢复 HTML 解析;
  5. 解析 CSS,构建 CSSOM 样式树;
  6. DOM + CSSOM 合并生成渲染树;
  7. 执行样式计算;
  8. 执行布局 Layout;
  9. 生成绘制 Paint 指令,交付给合成线程;
  10. 合成线程 → 光栅化线程 → 输出图像到屏幕。

为什么需要 Web Worker?

主线程所有任务排队执行,一旦执行耗时巨大的同步 JS 逻辑,主线程会被阻塞:页面无法响应点击、滚动、动画卡顿,也就是 UI 卡死。

传统方案局限性

  • setTimeout:只是延后执行,依旧跑在主线程,耗时计算依然阻塞 UI;
  • Promise:只是异步语法,没有开启新线程,计算逻辑仍然占用主线程;
  • requestIdleCallback:利用浏览器空闲碎片时间执行,不能实现真正并行。

适合交给 Web Worker 的任务

  • 海量数据循环、复杂算法计算、大数据转换;
  • 图像处理(配合 ImageBitmap 传输二进制数据);
  • 加解密、复杂数据校验;
  • 后台数据同步;
  • WebSocket 实时消息处理。

传统方案只是任务调度,不是多线程并行;Web Worker 就是浏览器提供的真正后台线程方案,把重计算丢到子线程,主线程专心负责页面渲染和交互。

Web Worker 基础使用

Web Worker 允许开辟独立子线程运行 JS 脚本,和主线程并行工作,不会阻塞 UI 渲染

核心特性

  • 独立操作系统线程,拥有自己的调用栈、全局上下文,不和主线程共享内存;
  • 不能访问 DOM API,没有 documentwindow
  • 线程间通信:依靠 postMessage()onmessage 传递数据,使用结构化克隆算法拷贝数据(不是共享引用)。
  • 同源策略限制:Worker 脚本必须和页面同源。
  • 拥有独立全局对象,Worker 内部使用 self 代表全局。

注意事项

  • Worker 线程也有开销,不要无限制大量创建,线程过多会带来 CPU、内存压力;
  • postMessage 是数据拷贝,传递超大对象性能损耗大,二进制优先使用 Transferable 转移;
  • Worker 不会自动销毁,不用的时候务必调用 terminate()(主线程)或者 self.close()(Worker 内部)释放资源;
  • Service Worker 专注网络缓存、PWA 场景,不适合做密集计算
  • Shared Worker 兼容性一般,业务尽量优先选择 Dedicated Worker。

Dedicated Worker(专用 Worker)

日常开发最常用,一对一绑定。

特点

  • 一对一:仅能被创建它的主线程使用,其他页面、iframe 无法访问;
  • 独立线程;
  • 全局对象:self,可以访问 navigator、只读 locationfetch、定时器。
// 主线程
const worker = new Worker('worker.js');
worker.postMessage('hello');
worker.onmessage = (e) => {};
worker.onerror = (error) => {};
worker.terminate();
// worker.js(Worker 线程)
self.onmessage = (e) => {};
self.onerror = (message, filename, lineno, colno, error) => {};
self.postMessage('world'); 
self.close();

Shared Worker(共享 Worker)

同源下多个页面、iframe 可以共享同一个 Worker 实例。

特点

  • 一对多:同源多个页面共用同一个 SharedWorker 实例;
  • 使用 port 端口对象通信,每一个连接对应独立 port;
  • 只要有端口连接,Worker 就保持存活;全部端口关闭,Worker 才销毁。
// 主线程(任意多个页面/脚本)
const sharedWorker = new SharedWorker('shared-worker.js');
sharedWorker.port.postMessage('hello');
sharedWorker.port.onmessage = (e) => {};
sharedWorker.onerror = (error) => {};
sharedWorker.port.start();
sharedWorker.port.close();
// shared-worker.js(Shared Worker 线程)
const ports = [];
self.onconnect = (event) => {
    const port = event.ports[0];
    ports.push(port);
    port.onmessage = (e) => {
        port.postMessage('收到: ' + e.data);
        ports.forEach(p => p.postMessage('广播: ' + e.data));
    };
    port.start();
    port.onclose = () => {};
};
self.onerror = (message, filename, lineno, colno, error) => {};

Service Worker(服务工作线程)

PWA 核心技术,网络代理线程,重点用于离线缓存、后台推送、后台同步,不适合做大量计算。

核心特点

  • 作为浏览器和服务器之间网络代理,可以拦截页面全部网络请求;
  • 独立后台线程,不绑定任意页面;页面关闭,Service Worker 依旧可以存活;
  • 强制 HTTPS(本地开发 localhost 例外);
  • 事件驱动,空闲时浏览器自动休眠节省资源;
  • 拥有完整生命周期:installactivatefetch
  • 存在作用域 scope,只管控指定路径下页面。
// 主线程(注册 Service Worker)
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js', { scope: '/' })
        .then(registration => {
            console.log('注册成功:', registration.scope);
        })
        .catch(error => {
            console.error('注册失败:', error);
        });

    navigator.serviceWorker.onmessage = (e) => {};
    navigator.serviceWorker.oncontrollerchange = () => {};
    navigator.serviceWorker.ready.then(registration => {});
}
// sw.js(Service Worker 线程)

self.oninstall = (event) => {
    event.waitUntil(
        caches.open('my-cache-v1').then(cache => cache.addAll(
            ['/', '/styles.css', '/script.js']
        ))
    );
};

self.onactivate = (event) => {
    event.waitUntil(
        caches.keys().then(keys => {
            return Promise.all(keys.filter(key => key !== 'my-cache-v1').map(key => caches.delete(key)));
        })
    );
};

self.onfetch = (event) => {
    event.respondWith(
        caches.match(event.request).then(response => response || fetch(event.request))
    );
};

self.onmessage = (event) => {    
    event.source.postMessage('收到消息');

    // 或者向所有客户端广播消息
    self.clients.matchAll().then(clients => {
        clients.forEach(client => client.postMessage('广播消息'));
    });
};

self.onerror = (message, filename, lineno, colno, error) => {};

Web Worker API 完整清单

Dedicated Worker(专用 Worker) 作为基准;SharedWorkerServiceWorker 存在部分差异会标注。

Worker 构造函数

const worker = new Worker(url, [options])
  • url:Worker 脚本资源地址,必须遵守同源策略;可以是相对路径、绝对 URL。
  • options(可选)配置对象:
    • type:脚本模式,classic(默认普通脚本)、module(ES Module 模块模式)
    • credentials:凭证策略,omitsame-origininclude;模块模式下才会生效
    • name:给 Worker 设置名字,用于调试,worker 内部可通过 self.name 获取

SharedWorker:new SharedWorker(url[, options]);通信必须通过 .port 对象完成。

ServiceWorker:不使用 new 构造函数实例化,通过 navigator.serviceWorker.register() 进行注册。

主线程端:Worker

实例属性

  • onmessage:监听 Worker 发送过来的消息;回调接收 MessageEvent 事件对象,e.data 为接收到的数据。
  • onmessageerror:消息序列化失败时触发,收到无法被结构化克隆解析的数据会执行该回调。
  • onerror:捕获 Worker 线程内部抛出的错误。

实例方法

  • postMessage(message[, transferList]):向 Worker 线程发送消息。
    • message:任意可被结构化克隆算法处理的数据
    • transferList:转移对象数组,用于 ArrayBuffer 这类 Transferable 对象;转移内存所有权之后,原线程无法再访问该内存。
  • terminate():主线程调用,立刻终止 Worker 线程;会直接停止执行,不会等待当前正在运行的脚本完成。

SharedWorker 差异:SharedWorker 实例本身不存在 onmessagepostMessage()terminate();所有消息操作全部需要通过 sharedWorker.port 端口对象调用。

Worker 子线程全局作用域

Worker 内部,self 代表 Worker 的全局对象,等价于 this

全局事件处理器

  • self.onmessage:接收主线程发送过来的消息。
  • self.onmessageerror:解析收到的消息失败时触发。
  • self.onerror:Worker 线程内部的全局错误捕获。

其他 Worker 差异:

  • SharedWorker:使用 port.onmessage;全局监听新页面连接使用 self.onconnect
  • ServiceWorker:具备专属事件:installactivatefetchpushsync

全局作用域方法

  • self.postMessage(data[, transferList]):Worker 向主线程发送消息,支持传入转移列表。
  • self.close()Worker 线程内部调用关闭自身;停止接收新消息队列任务,但不会立刻中断正在同步执行的代码。
  • importScripts(url1, url2, …)仅 Dedicated Worker 可用,同步加载并执行外部 JS 脚本,会阻塞当前 Worker 线程;SharedWorker、ServiceWorker 不支持该 API。

全局只读属性

  • self.name:构造函数 options 配置传入的 worker 名称。

支持的 API

  • JavaScript 核心功能(全部支持):标准的 JavaScript 函数集(如 String、Array、Object、JSON 等),Array、Object、Map、Set、Promise、Symbol、ArrayBuffer、DataView、TypedArray、正则表达式、箭头函数、类、模块。
  • 定时器函数:setTimeout、clearTimeout、setInterval、clearInterval。注意:不支持 setImmediate(非标准)。
  • 网络请求 API:fetch、XMLHttpRequest、WebSocket。
  • 存储 API:caches(Service Worker 专用,其他 Worker 也支持)、indexedDB。
  • 编解码 API:TextEncoder、TextDecoder、btoa、atob。
  • 二进制数据处理:Blob、FileReader(部分支持,无文件选择功能)、File/FileList(不支持,无法访问用户文件)。
  • 加密 API(部分支持):Crypto(支持 getRandomValues,但不支持 subtle)。
  • URL 相关 API:URL、URLSearchParams。
  • 导航器信息(部分):支持 userAgent、language、onLine、serviceWorker(仅安全上下文);不支持 geolocation, clipboard, mediaDevices, permissions 等。
  • 其他支持的 API:console、performance、BroadcastChannel、EventSource。

不支持的 API

  • DOM 相关(完全不支持):document、window、parent、top、opener、DOM 元素操作。
  • 渲染相关:requestAnimationFrame、canvas、WebGL、CSS 对象模型。
  • 大多数交互 API:alert、confirm、prompt、localStorage、sessionStorage、Clipboard、Geolocation、Notification、Payment Request、Speech、Fullscreen、History。
  • 部分其他 API:WebUSB、WebBluetooth、WebSerial、IntersectionObserver、ResizeObserver、MutationObserver(部分浏览器可能支持)。

三种 Worker 关键差异

Dedicated Worker:

  • 创建方式:new Worker()
  • 通信入口:实例直接调用 postMessage;
  • 支持 importScripts()
  • 终止方式:主线程 terminate()、Worker 内部 self.close()
  • 无额外专属事件;
  • 使用场景:大数据密集计算。

Shared Worker:

  • 创建方式:new SharedWorker()
  • 通信入口:必须通过 .port 端口对象;
  • 不建议使用 importScripts
  • 终止方式:关闭 port 端口连接;
  • 专属事件:onconnect
  • 使用场景:同源多个页面共享同一个后台线程。

Service Worker:

  • 创建方式:navigator.serviceWorker.register() 注册;
  • 通信入口:主线程 navigator.serviceWorker.onmessage
  • 禁止使用 importScripts
  • 生命周期:浏览器自动休眠;内部使用 self.skipWaiting()
  • 专属事件:installactivatefetchpush
  • 使用场景:PWA、离线缓存、网络代理、后台消息推送。

小结

  • JS 语言单线程,阻塞根源在主线程;Web Worker 是浏览器提供的子线程能力,实现真正并行。
  • Dedicated Worker:一对一,适合大量计算,业务最常用;
  • Shared Worker:同源多页面共享;
  • Service Worker:PWA 网络代理,离线缓存、消息推送;
  • Worker 没有 DOM,线程间依靠 postMessage 消息通信。

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-23 18:27:37

results matching ""

    No results matching ""