JavaScript高级程序设计笔记 27

第 27 章 工作者线程

学习目标:理解浏览器工作者线程的类型、通信方式、数据传输、服务工作者线程缓存与生命周期,以及它们适合解决的问题。

1. 本章核心脉络

JavaScript 主线程负责执行脚本、处理 DOM、响应用户交互和渲染相关任务。耗时计算会阻塞主线程,造成页面卡顿。工作者线程允许把部分任务放到后台线程执行。

本章主要介绍三类 worker:

  • 专用工作者线程 DedicatedWorker:只服务创建它的页面。
  • 共享工作者线程 SharedWorker:可被同源多个页面共享。
  • 服务工作者线程 ServiceWorker:位于页面和网络之间,可拦截请求、管理缓存、支持离线和推送。

2. 工作者线程简介

2.1 Worker 与线程

worker 运行在独立线程中,有自己的全局作用域,不会阻塞主线程。

它适合:

  • CPU 密集型计算。
  • 大量数据处理。
  • 文件解析。
  • 图片处理。
  • 后台任务调度。

不适合:

  • 直接操作 DOM。
  • 频繁与主线程同步的小任务。
  • 依赖大量主线程状态的逻辑。

2.2 Worker 类型

类型 作用
Worker 专用 worker,一对一服务页面
SharedWorker 多个同源页面共享
ServiceWorker 拦截网络、缓存资源、离线能力

2.3 WorkerGlobalScope

worker 内部没有 window,它的全局作用域是 WorkerGlobalScope 的子类型。

worker 中可用:

  • self
  • postMessage()
  • onmessage
  • fetch()
  • setTimeout()
  • setInterval()
  • 部分 Web API

worker 中不可直接使用:

  • document
  • window
  • DOM 操作 API

3. 专用工作者线程

3.1 创建 Worker

主线程:

const worker = new Worker("./worker.js");

worker.postMessage({ type: "start", payload: 100000 });

worker.onmessage = (event) => {
  console.log("result:", event.data);
};

worker.js:

self.onmessage = (event) => {
  const { payload } = event.data;
  self.postMessage(payload * 2);
};

3.2 隐式 MessagePort

主线程和专用 worker 之间通过消息机制通信。postMessage() 会把数据发送到另一端,另一端通过 message 事件接收。

特点:

  • 异步通信。
  • 数据会经过结构化克隆。
  • 不能共享普通对象引用。

3.3 生命周期

worker 创建后会加载脚本并执行。可以通过 terminate() 从主线程终止 worker:

worker.terminate();

worker 内部也可以主动关闭:

self.close();

实践建议:

  • 长时间不用的 worker 应及时终止。
  • 避免创建过多 worker。
  • 错误和结束状态要有管理逻辑。

3.4 Worker 选项

可以创建模块 worker:

const worker = new Worker("./worker.js", {
  type: "module",
  name: "calc-worker",
});

模块 worker 支持 import,更适合现代项目。

3.5 行内创建 Worker

可以用 Blob 动态创建 worker 脚本:

const code = `
  self.onmessage = (event) => {
    self.postMessage(event.data * 2);
  };
`;

const blob = new Blob([code], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));

适合小型临时 worker,但可维护性不如独立文件。

3.6 动态执行脚本

传统 worker 可以使用 importScripts() 加载额外脚本:

importScripts("utils.js");

模块 worker 中应优先使用标准 import

3.7 子 worker

worker 内部可以再创建 worker,把任务继续拆分。但要注意线程数量、资源消耗和调试复杂度。

3.8 错误处理

主线程监听 worker 错误:

worker.onerror = (event) => {
  console.error(event.message, event.filename, event.lineno);
};

worker 内部也可以捕获错误并主动通知主线程:

try {
  // task
} catch (error) {
  self.postMessage({ type: "error", message: error.message });
}

3.9 数据传输

默认情况下,postMessage() 使用结构化克隆算法。大对象会有复制成本。

worker.postMessage({ items: [1, 2, 3] });

对于 ArrayBuffer,可以使用可转移对象避免复制:

const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]);

转移后,原线程中的 buffer 会失效。

3.10 共享内存

SharedArrayBuffer 可以让多个线程访问同一块内存,通常配合 Atomics 做同步控制。

这是高性能但复杂的能力,适合对性能要求极高的场景。普通业务中优先使用消息传递。

3.11 线程池

频繁创建和销毁 worker 有成本。对于大量可拆分任务,可以维护 worker 池。

线程池要处理:

  • 最大并发数。
  • 任务队列。
  • 空闲 worker 复用。
  • 错误恢复。
  • 结果回调。

4. 共享工作者线程

4.1 SharedWorker 简介

SharedWorker 可以被同源的多个页面、iframe 或 worker 共享。

主线程:

const worker = new SharedWorker("./shared-worker.js");

worker.port.start();
worker.port.postMessage("hello");

worker.port.onmessage = (event) => {
  console.log(event.data);
};

shared-worker.js:

self.onconnect = (event) => {
  const port = event.ports[0];

  port.onmessage = (messageEvent) => {
    port.postMessage(messageEvent.data);
  };
};

4.2 生命周期

共享 worker 的生命周期由连接它的页面共同影响。只要仍有页面连接,它就可能继续存在。

适合:

  • 多标签页共享连接。
  • 多页面共享缓存。
  • 同源页面之间协调状态。

5. 服务工作者线程

5.1 Service Worker 基础

ServiceWorker 是一种特殊 worker,运行在页面和网络之间,可以拦截请求、响应缓存、处理推送和后台同步。

注册:

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/service-worker.js");
}

特点:

  • 必须在安全上下文中使用,通常要求 HTTPS。
  • 与页面生命周期分离。
  • 不能访问 DOM。
  • 事件驱动,空闲时可能被浏览器终止。

5.2 生命周期

核心阶段:

  • install:安装,通常用于预缓存资源。
  • activate:激活,通常用于清理旧缓存。
  • fetch:拦截页面网络请求。

示例:

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("v1").then((cache) => {
      return cache.addAll(["/", "/styles.css", "/main.js"]);
    })
  );
});

5.3 Cache Storage

Service Worker 常与 Cache Storage 配合使用。

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request);
    })
  );
});

常见缓存策略:

  • Cache First:优先缓存,适合静态资源。
  • Network First:优先网络,适合实时数据。
  • Stale While Revalidate:先用缓存,同时后台更新。
  • Network Only:只走网络。
  • Cache Only:只走缓存。

5.4 Service Worker 客户端

Service Worker 可以通过 clients API 获取受控页面,并向页面发送消息。

self.clients.matchAll().then((clients) => {
  clients.forEach((client) => {
    client.postMessage("updated");
  });
});

页面接收:

navigator.serviceWorker.addEventListener("message", (event) => {
  console.log(event.data);
});

5.5 一致性问题

Service Worker 的缓存能力强,但也容易出现“代码已更新,页面还在用旧资源”的问题。

实践建议:

  • 给缓存命名加版本号。
  • activate 中清理旧缓存。
  • 谨慎使用长时间缓存 HTML。
  • 更新后提示用户刷新。
  • 理解 skipWaiting()clients.claim() 的影响。

5.6 fetch 事件拦截

fetch 事件可以决定请求如何响应:

self.addEventListener("fetch", (event) => {
  if (event.request.method !== "GET") {
    return;
  }

  event.respondWith(
    fetch(event.request).catch(() => caches.match(event.request))
  );
});

注意:

  • 不要缓存不该缓存的敏感响应。
  • POST 等非幂等请求要谨慎处理。
  • 对跨源资源缓存要考虑 CORS 响应类型。

5.7 推送通知

Service Worker 可处理服务器推送:

self.addEventListener("push", (event) => {
  const data = event.data?.json() || {};

  event.waitUntil(
    self.registration.showNotification(data.title || "通知", {
      body: data.body,
    })
  );
});

适合消息提醒、状态变更通知等场景。

6. Worker 选择建议

场景 推荐
大计算任务避免主线程卡顿 Dedicated Worker
多标签页共享后台状态 SharedWorker
离线缓存、请求拦截 Service Worker
实时推送通知 Service Worker
简单 UI 状态处理 不需要 Worker

7. 面试高频问题

7.1 Worker 能操作 DOM 吗?

不能。worker 运行在独立线程中,没有 document 和 DOM API。需要通过消息把结果传回主线程,再由主线程更新 DOM。

7.2 postMessage 传对象是共享引用吗?

不是。默认通过结构化克隆传递,会复制数据。ArrayBuffer 等可转移对象可以转移所有权,避免复制成本。

7.3 Service Worker 和普通 Worker 有什么区别?

普通 worker 主要用于后台计算。Service Worker 位于页面和网络之间,可以拦截请求、管理缓存、支持离线和推送,生命周期也不依附于某个页面。

7.4 Service Worker 为什么需要 HTTPS?

因为它能拦截和伪造页面请求,权限很高。HTTPS 可以防止注册脚本被中间人篡改。localhost 通常作为开发例外。

7.5 什么情况下应该使用 worker?

当任务会明显阻塞主线程,或者需要离线缓存、后台推送、请求代理等能力时适合使用。普通轻量逻辑不必为了“多线程”而增加复杂度。

8. 复习清单

9. 一句话总结

第 27 章的核心是把浏览器能力从单主线程扩展到后台线程:普通 worker 解决计算阻塞,Service Worker 解决网络拦截、离线缓存和推送,但它们都需要用清晰的消息边界和生命周期管理来控制复杂度。

posted @ 2025-08-19 10:42  Li_pk  阅读(7)  评论(0)    收藏  举报