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

JavaScript 设计之初就是为了操作浏览器 DOM 和处理用户交互。如果允许多线程同时修改同一个 DOM 节点,一个线程要删除,另一个线程要修改,浏览器就不知道听谁的了。因此,JS 的主线程是单线程的,所有代码在一个主线程上按顺序执行。

JavaScript 是单线程语言,但它的运行环境(浏览器、Node.js)提供了实现多线程效果的能力:

  • 浏览器中的 Web Workers:在浏览器后台创建独立于主线程的 Worker 线程

  • Node.js 中的 Worker Threads:类似 Web Workers,用于在服务器端实现真正的并行计算。

浏览器进程和线程

进程(Chrome)

浏览器进程

全局控制中心,不渲染网页内容,但管理所有标签页。

  • UI 线程:绘制 UI 界面(地址栏、书签、前进/后退按钮)、响应用户点击/输入。
  • 存储线程:读写 localStorage、IndexedDB、Cookie、文件系统。
  • 进程通信线程(IPC):负责浏览器各进程的消息传递。
  • 文件 IO 线程:处理文件读写。
渲染进程

解析 HTML/CSS、执行 JavaScript、页面布局与绘制、处理用户交互。每个页签(或 iframe)通常有自己独立的渲染进程。

  • 主线程:执行 JavaScript 代码、解析 HTML/CSS 构建 DOM 树和 CSSOM 树、执行样式计算、布局(Layout)、绘制记录(Paint),处理用户事件回调,管理宏任务与微任务的执行。
  • 合成线程:处理滚动和 CSS 动画,将各图层位图合成为最终屏幕图像。
  • 光栅化线程池:将矢量图形、图层、绘制指令转换为实际的位图像素数据。
  • Worker 线程:运行 Web Worker、Service Worker、Shared Worker,无法直接访问 DOM,可与主线程通信。
  • 音频线程:处理音频解码、混合和输出。
  • 视频解码线程:视频流硬件或软件解码。
GPU 进程

专门负责图形相关的加速任务(3D 加速、CSS 动画、video 解码等)。

GPU 的使用初衷是为了实现 CSS 的 3D 效果,只是随后网页、UI 界面都选择采取 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。

  • GPU 主线程:调用底层图形驱动接口(OpenGL/Metal/DirectX/Vulkan),管理 GPU 内存中的纹理、缓冲区、着色器等资源。
  • 合成线程:处理 CSS 3D 变换、WebGL 等,将多个图层合成为一帧完整的图像。
  • 显示线程:将合成完成的帧输出到显示器。
网络进程

独立处理所有网络请求(HTTP/HTTPS 等),包括缓存、Cookie、跨域策略等。

  • 网络调度线程:管理请求队列、决定请求优先级、复用已有连接(Keep-Alive、HTTP/1.1 连接复用)。
  • HTTP/2 线程:处理 HTTP/2 协议、管理多路复用(多个请求共享一个 TCP 连接)、处理头部压缩(HPACK 算法)。
  • QUIC/HTTP/3 线程:处理基于 UDP 的 QUIC 协议及 HTTP/3 请求,解决 TCP 队头阻塞问题。
  • 缓存线程:读取和写入 HTTP 缓存,依据 Cache-Control、ETag 等头部判断缓存有效性。
  • 代理线程:处理各种代理协议(SOCKS4、SOCKS5、HTTP 代理),负责代理环境下的请求转发和隧道建立。
插件进程

用于运行旧的浏览器插件(如基于 NPAPI 或 PPAPI 的 Flash 插件、PDF 查看器)。随着 Web 技术发展,目前已很少使用。

  • 插件主线程:运行插件的核心业务逻辑(如 Flash 脚本或 PDF 解析代码),处理插件生命周期。
  • 图形线程:负责插件的图形输出(如 Flash 动画或游戏画面的绘制)。
  • 音频线程:处理插件发出的音频流(如 Flash 中的声音播放)。

渲染器进程

渲染器进程是浏览器多进程架构中最复杂、与网页开发者关系最密切的进程。每个标签页(或同源 iframe 组)通常拥有一个独立的渲染器进程,这种隔离机制保证了一个页面的崩溃不会影响其他页面。

核心职责

  • 解析 HTML 文档并构建 DOM 树。
  • 解析 CSS 并构建 CSSOM 树。
  • 执行 JavaScript 代码。
  • 计算样式、执行布局(Layout/Reflow)。
  • 进行绘制记录(Paint)和光栅化(Rasterization)。
  • 处理用户交互事件。
  • 管理网络请求(部分与网络进程协作)。
  • 运行各种工作线程(Worker)。

内部线程

  • 主线程:核心执行线程,承担最多任务。
  • 合成线程:独立处理滚动和部分动画。
  • 光栅化线程池:将绘制指令转换为像素位图。
  • Worker 线程:包括 Web Worker、Service Worker、Shared Worker。
  • 音频线程:处理 Web Audio API。
  • 视频解码线程:硬件/软件解码视频流。

JavaScript 主线程

JavaScript 代码在主线程上执行,——JavaScript 是单线程的,也就是主线程只有一个,

主线程特性

  • 同一时刻只能做一件事。
  • 所有同步任务按顺序执行。

  • 异步任务通过事件循环(Event Loop)机制调度。

主线程工作

  • avaScript 代码执行:执行全局同步代码、执行任务队列中的任务。
  • 页面解析与构建:HTML 解析、CSS 解析、合并为渲染树。
  • 样式计算:遍历渲染树中的每个元素,计算最终的样式属性。
  • 布局:计算每个元素在页面中的精确位置和尺寸。
  • 绘制记录:将布局阶段得到的位置和尺寸信息,转换为绘制指令。
  • 事件处理:捕获并分发用户交互事件、处理事件冒泡和事件捕获、执行事件的回调函数。
  • 任务队列调度(事件循环):维护一个事件循环,持续不断地检查是否有任务需要执行,包括:宏任务队列、微任务队列。
  • 线程协作:负责与其他线程的协作。

主线程执行流程

  • 用户输入 URL 回车。
  • 主线程接收网络进程传来的 HTML 数据。
  • 解析 HTML(构建 DOM 树),遇到 script 标签则暂停解析。
  • 加载并执行 JavaScript,可能修改 DOM 和 CSSOM。
  • 继续解析 HTML 剩余部分。
  • 解析 CSS,构建 CSSOM 树。
  • 将 DOM + CSSOM 合并为渲染树。
  • 样式计算。
  • 布局。
  • 绘制记录。
  • 将绘制指令交给合成线程。
  • 合成线程 → 光栅化 → 显示。

为什么需要 Web Worker?

JavaScript 是单线程语言,当 JavaScript 在执行耗时任务时,浏览器无法做任何其他事情——不能响应用户点击,不能渲染动画,不能滚动页面。

这就是典型的 UI 阻塞问题。

传统解决方案:

  • setTimeout:只是延迟执行,还是会阻塞。
  • Promise:异步但不并行,还是单线程。
  • requestIdleCallback:利用空闲时间,但不能真正并行。

适用于 Worker 的任务类型:

  • 大量数据计算和转换。
  • 图像处理(通过 ImageBitmap 传输)。
  • 数据加密/解密。
  • 复杂数据验证。
  • 后台数据同步。
  • 实时数据处理(WebSocket + Worker)。

这些需要的是真正的后台任务——这就是 Web Worker 诞生的原因。

Worker

Web Worker 是浏览器提供的 JavaScript 多线程解决方案,允许在后台线程中运行脚本,不会阻塞主线程的 UI 渲染和用户交互。

核心特征

  • 运行在独立线程:不与主线程共享执行栈,不会阻塞主线程。
  • 无法访问 DOM:不能操作 document、window、parent 等对象(但可以访问部分 API,如 navigator、location、fetch、IndexedDB、WebSocket)。
  • 通信通过消息传递:与主线程之间通过 postMessage 和 onmessage 交换数据。
  • 同源限制:Worker 脚本必须与父页面同源(除非使用 CORS)。
  • 作用域独立:Worker 有自己的全局上下文,与主线程的 window 不同。

构造函数

const worker = new Worker(URL, options);

构造函数参数:

  • URL:Worker 脚本的 URL 地址(同源策略限制)。
  • options(可选):
    • type:脚本类型,默认 "classic",可设为 "module"
    • credentials:凭证选项,"omit""same-origin""include"
    • name:Worker 的名称,用于调试。

实例属性

  • onerror:错误事件处理器,当 Worker 发生错误时触发。
  • onmessage:消息事件处理器,接收 Worker 发送的消息。
  • onmessageerror:消息错误事件处理器,当接收到无法反序列化的消息时触发。

实例方法

  • postMessage():向 Worker 发送消息,支持结构化克隆算法传输的数据。
  • terminate():立即终止,Worker 线程。

Worker 全局作用域方法

  • postMessage():向创建 Worker 的主线程发送消息。
  • close():立即终止 Worker 自身。
  • importScripts():同步导入一个或多个脚本(仅在 Dedicated 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:localStorage / sessionStorage、WebUSB、WebBluetooth、WebSerial、IntersectionObserver、ResizeObserver、MutationObserver(部分浏览器可能支持)。

Dedicated Worker(专用)

Dedicated Worker(专用工作线程)是由单个脚本(通常是主线程)创建和使用的 Worker。它只能被创建它的那个脚本访问,无法被其他脚本(包括同源的其他页面或 Worker)使用。

  • 1对1绑定:一个 Dedicated Worker 只服务于创建它的脚本。
  • 独立线程:运行在独立于主线程的真实操作系统线程中。
  • 消息通信:通过 postMessage() 和 onmessage 进行异步消息传递。
  • 无法访问:DOM 不能操作 document、window、parent 等。
  • 可用的全局对:self(指向 Worker 自身)、navigator、location(只读)、fetch、XMLHttpRequest、setTimeout 等。
  • 同源限制 Worker:脚本必须与主页面同源(除非使用 CORS 跨域加载)。
// 主线程
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(共享)

Shared Worker(共享工作线程)是一种可以被多个脚本(如同源的不同页面、iframe 或 Worker)共同使用的 Worker。只要这些脚本处于同源环境中,它们就可以共享同一个 Shared Worker 实例。

核心特征

  • 1对多绑定:一个 Shared Worker 可以被同源的多个脚本同时访问。
  • 独立线程:运行在独立于主线程的真实操作系统线程中。
  • 端口通信:通过 port 进行消息传递,每个连接有独立的端口。
  • 无法访问 DOM:不能操作 documentwindowparent 等。
  • 可用的全局对象selfnavigatorlocation(只读)、fetch 等。
  • 同源限制:必须与所有连接的页面同源。
  • 生命周期:只要还有任何端口连接,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

Service Worker(服务工作线程)是一种代理服务器,位于浏览器和网络之间,可以拦截、处理所有网络请求。它主要用于实现离线访问、推送通知和后台同步等功能,是渐进式 Web 应用(PWA)的核心技术。

Service Worker 运行在独立的后台线程中,不与任何特定页面绑定,可以同时服务于多个页面。

核心特征

  • 网络代理:作为浏览器和网络之间的代理服务器,可以拦截、修改所有网络请求。
  • 离线支持:缓存资源后,即使在离线状态下也能正常访问页面。
  • 独立线程:运行在独立于主线程的后台线程中,不会阻塞页面交互。
  • 无法访问 DOM:不能操作 documentwindowparent 等。
  • 可用的全局对象selfcachesfetchclientsCacheStoragenavigatorlocation(只读)等。
  • HTTPS 限制:出于安全考虑,必须在 HTTPS 协议下运行(本地开发 localhost 除外)。
  • 事件驱动:基于事件触发,不使用时自动休眠,节省系统资源。
  • 独立生命周期:有独立的注册、安装、激活、更新等生命周期阶段,与页面生命周期解耦。
  • 作用域限制:只能控制注册路径及其子路径下的页面。
  • 推送通知:支持后台推送消息,即使页面未打开也能接收通知。
  • 后台同步:支持网络恢复后自动重试失败的操作。

注意:Service Worker 注册的默认作用域是与 Service Worker 脚本文件路径相对的 ./。如果 Service Worker 脚本文件位于根网域, 就意味着其作用域将是整个域名;如果位于 /example/ ,则它的默认作用域为 /example/,即只能管控 /example/ 目录下的页面。

// 主线程(注册 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 线程)

/**
 * 安装事件(install)
 * 触发时机:Service Worker 首次注册成功或版本更新时
 * 版本更新:sw.js 文件内容发生变化。可以是修改版本号常量、添加注释、修改代码逻辑等
 * 用途:预缓存静态资源,为离线访问做准备
 */
self.oninstall = (event) => {
    // waitUntil() 方法用于延长安装事件的生命周期
    // 接收一个 Promise,只有当 Promise 完成后,安装才算成功
    event.waitUntil(
        // caches.open() 打开或创建指定的缓存库,my-cache-v1 是缓存名称
        // cache.addAll() 批量添加资源到缓存。任意一个资源下载失败,整个安装会失败
        caches.open('my-cache-v1').then(cache => cache.addAll([
            '/',              // 根页面(通常是 index.html)
            '/styles.css',    // 样式文件
            '/script.js'      // 脚本文件
        ]))
    );
};

/**
 * 激活事件(activate)
 * 触发时机:install 安装成功后,或新的 Service Worker 开始接管页面时
 * 用途:清理旧缓存、执行迁移操作、立即接管页面
 */
self.onactivate = (event) => {
    event.waitUntil(
        // caches.keys() 获取所有缓存库的名称列表
        // 只保留当前版本 'my-cache-v1',其他版本都删除
        caches.keys().then(keys => {
            return Promise.all(keys.filter(key => key !== 'my-cache-v1').map(key => caches.delete(key)));
        })
    );

    // 默认情况下,新 Service Worker 激活后,不会立即控制已打开的页面,需要用户刷新页面或关闭重开后,新页面才会被新 SW 控制。
    // clients.claim() 可选:让新的 Service Worker 立即接管所有未受控的页面
    // event.waitUntil(clients.claim());
};

/**
 * 请求拦截事件(fetch)
 * 触发时机:页面发起任何网络请求(fetch、XMLHttpRequest、图片、CSS、JS 等)
 * 用途:拦截请求,决定从缓存返回、从网络请求、或自定义响应
 */
self.onfetch = (event) => {
    // respondWith() 用于拦截网络请求,改为执行内部逻辑
    // caches.match() 在缓存中查找匹配当前请求的资源
    // 如果缓存命中,返回缓存内容;否则发起真实网络请求
    event.respondWith(
        caches.match(event.request).then(response => response || fetch(event.request))
    );
};

self.onmessage = (event) => {    
    // event.source 指向发送消息的页面
    event.source.postMessage('收到消息');

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

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

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-06-01 22:54:47

results matching ""

    No results matching ""