一.web Worker 定义
js最初设计是运行在浏览器中的,为了防止多个线程同时操作DOM,带来渲染冲突问题,所以js执行器被设计成单线程。
但随着前端技术的发展,js能力远不止如此,当我们遇到需要大量计算的场景时(比如图像处理、视频解码等),js线程往往会被长时间阻塞,甚至造成页面卡顿,影响用户体验。
为了解决单线程带来的这一弊端,Web Worker 应运而生。
web worker三类
Web Worker分为三类。Dedicated Worker、Shared Worker和Service Worker。
Dedicated Worker:每个Dedicated Worker都与创建它的脚本线程相关联,它们之间是一对一的关系。这意味着每个Dedicated Worker只能被一个脚本使用。Shared Worker:可以由在不同窗口、IFrame 等中运行的多个脚本使用的 Worker,只要它们与 Worker 在同一域中。它们比专用的 Worker 稍微复杂一点——脚本必须通过活动端口进行通信。Service Worker:基本上是作为代理服务器,位于 web 应用程序、浏览器和网络(如果可用)之间。它们的目的是(除开其他方面)创建有效的离线体验、拦截网络请求,以及根据网络是否可用采取合适的行动并更新驻留在服务器上的资源。它们还将允许访问推送通知和后台同步 API。Service Worker是实现PWA的重要一环。
二. Web worker的限制
无法访问DOM元素
也很好理解,js最初设计就有防止多个线程同时操作DOM
无法直接访问 window 对象
Web Worker 运行在一个完全独立的线程中,它没有 window 对象,因此无法访问主线程中的 window、document、localStorage、sessionStorage 等 Web API。
三.web worker 的基本使用
Worker 线程上下文也存在一个顶级对象 self。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
const worker = new Worker("worker.js");
worker.onmessage = function (e) {
console.log("从 worker 线程收到的消息:", e.data);
};
worker.postMessage("Hello, Worker!");
</script>
</body>
</html>
// worker.js
onmessage = function (e) {
console.log("从主线程收到的消息:", e.data); // 输出主线程发来的数据
postMessage("Hello, Main Thread!"); // 返回消息给主线程
};
//main.js(主线程)
const worker = new Worker("worker.js");
worker.terminate()
// worker.js(worker线程) self.close(); // 直接执行close方法就ok了
四.Web Worker 工作原理
浏览器的多进程架构
首先,了解现代浏览器的多进程架构有助于理解 Web Worker 的位置:
- 浏览器进程:管理浏览器的用户界面、地址栏、书签栏等
- 渲染进程:负责网页内容的渲染,每个标签页通常有独立的渲染进程
- 插件进程:管理浏览器插件
- GPU 进程:处理 GPU 任务,加速渲染
- 网络进程:处理网络请求
Web Worker 在渲染进程内部运行,但与主线程(也称为 UI 线程)分离。
五.Web Worker的数据传递
structedClone(结构化克隆算法)
tructedClone(结构化克隆算法)
由HTML标准定义,它可以涵盖JSON.stringify()能够序列化的一切值,还支持很多其它JavaScript类型,比如Map、Set、Date、RegExp和TypedArray,它甚至还能处理包含循环引用的数据结构。
通信效率问题
-
无法共享内存: 与传统多线程编程不同,Web Worker不能直接共享内存,主页面与worker之间的数据传递的是通过
拷贝而不是共享来完成的。因此在如大文件传输场景下可能会消耗大量内存和处理时间。 -
数据序列化问题: 当主线程与Web Worker之间在数据交换时需要对数据进行序列化和反序列化,
序列化会阻塞发送方,而反序列化会阻塞接收方。因此即使是小型数据,也需要经过这一过程。这可能在频繁通信的情况下积累成为性能瓶颈。 -
postMessage:
postMessage方法本身并不是导致通信效率低下的主要原因,而是由于如Worker线程需要频繁地向主线程发送大量消息,或者消息体积较大等其他因素造成。这可能会导致主线程处理消息的速度跟不上Worker线程发送消息的速度,从而引起通信拥塞和性能问题。
Transferable Object(可转移对象)
postMessage()方法还接收可选的第二个参数,该参数是一个数组,数组的元素不是被复制到Worker中,而是被转移到Worker中。
// 创建并填充一个8M的文件
const uInt8Array = new Uint8Array(1024 * 1024 * 8).map((v, i) => i);
console.log(uInt8Array.byteLength); // 8388608
// 把文件转移到work线程中
worker.postMessage(uInt8Array, [uInt8Array.buffer]);
console.log(uInt8Array.byteLength); // 0
SharedArrayBuffer (内存共享)
SharedArrayBuffer 是共享内存,不同线程可以同时访问和操作同一块内存空间。(需要加同步锁)
const sab = new SharedArrayBuffer(1024);
worker.postMessage(sab);