无阻塞UI:通过Web Worker提升用户体验的新途径

news/2025/3/26 2:55:44/

1.Web Worker 的定义

Web Worker 是浏览器提供的 JavaScript 多线程解决方案,允许在主线程之外运行脚本,执行耗时任务而不阻塞用户界面, 适用于处理耗时任务,避免阻塞主线程,提升用户体验。通过 postMessage 和 onmessage 实现主线程与 Worker 的通信.

2.使用场景

  1. 复杂计算:如图像处理、数据加密等。

  2. 大数据处理:如排序、过滤等。

  3. 实时通信:如 WebSocket 或轮询服务器。

  4. 后台任务:如日志上传、数据同步。

3.语法

1. 创建 Web Worker
javascript">// 主线程
const worker = new Worker('worker.js');
2. 主线程与 Worker 通信
  • 主线程发送消息
    javascript">worker.postMessage('Hello Worker');
  • 主线程接收消息
    javascript">worker.onmessage = function(event) {console.log('Received from worker:', event.data);
    };
3. Worker 脚本
  • 接收消息
    javascript">self.onmessage = function(event) {console.log('Received from main thread:', event.data);
    };
  • 发送消息
    javascript">self.postMessage('Hello Main Thread');
4. 终止 Worker
javascript">worker.terminate();

4.主要API

Web Worker 除了基本的 postMessage 和 onmessage 之外,还提供了其他一些 API 和方法,用于更灵活地控制 Worker 的行为和通信。以下是一些常用的 Web Worker API 和方法:

1. Worker 构造函数 ★ 常用

用于创建一个新的 Web Worker。

javascript">const worker = new Worker('worker.js');

2. postMessage ★ 常用

用于向 Worker 发送消息。

javascript">worker.postMessage(data);// data 可以是任意类型的数据(如字符串、对象、数组等)。// 数据会被结构化克隆算法(Structured Clone Algorithm)序列化后传递给 Worker。

3. onmessage ★ 常用

用于监听 Worker 发送的消息。

javascript">worker.onmessage = function(event) {console.log('Received from worker:', event.data);
};// event.data 是 Worker 发送的数据。

4. onerror ★ 常用

用于监听 Worker 中的错误。

javascript">worker.onerror = function(error) {console.error('Worker error:', error);
};// error 是一个 ErrorEvent 对象,包含错误的详细信息。

5. terminate ★ 常用

用于立即终止 Worker。

javascript">worker.terminate(); // 终止后,Worker 会停止运行,无法再接收或发送消息。

6. importScripts

在 Worker 中动态加载外部脚本。

javascript">// worker.js
importScripts('script1.js', 'script2.js');// 可以加载多个脚本,脚本会按顺序执行。// 加载的脚本必须是同源的。

7. close ★ 常用

在 Worker 内部调用,用于关闭 Worker。

javascript">// worker.js
self.close();// Worker 会立即停止运行,无法再接收或发送消息

8. MessageChannel

用于在主线程和 Worker 之间创建双向通信通道。

javascript">// 主线程
const channel = new MessageChannel();// 发送端口给 Worker
worker.postMessage({ port: channel.port1 }, [channel.port1]);// 监听消息
channel.port2.onmessage = function(event) {console.log('Received from worker:', event.data);
};// 发送消息
channel.port2.postMessage('Hello Worker');
javascript">// worker.js
self.onmessage = function(event) {const port = event.ports[0];// 监听消息port.onmessage = function(event) {console.log('Received from main thread:', event.data);};// 发送消息port.postMessage('Hello Main Thread');
};

9. BroadcastChannel

用于在多个 Worker 或主线程之间广播消息。

javascript">// 主线程或 Worker
const channel = new BroadcastChannel('my-channel');// 发送消息
channel.postMessage('Hello everyone!');// 监听消息
channel.onmessage = function(event) {console.log('Received:', event.data);
};

10. SharedArrayBuffer 和 Atomics

用于在主线程和 Worker 之间共享内存。

javascript">// 主线程
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);// 发送共享内存给 Worker
worker.postMessage(sharedBuffer);// 修改共享内存
Atomics.store(sharedArray, 0, 123);
javascript">// worker.js
self.onmessage = function(event) {const sharedBuffer = event.data;const sharedArray = new Int32Array(sharedBuffer);// 读取共享内存const value = Atomics.load(sharedArray, 0);console.log('Shared value:', value);
};

11. Worker 生命周期事件 ★ 重要

Worker 支持以下生命周期事件:

  • onmessage:接收消息。

  • onerror:捕获错误。

  • onmessageerror:当接收到的消息无法反序列化时触发。

    javascript">worker.onmessageerror = function(event) {console.error('Message error:', event);
    };

12. Worker 全局作用域

在 Worker 中,self 指向 Worker 的全局作用域。常用的属性和方法包括:

  • self.postMessage:发送消息。

  • self.onmessage:监听消息。

  • self.close:关闭 Worker。

  • self.importScripts:加载外部脚本。

完整示例:使用 MessageChannel 实现双向通信

以下是一个完整示例,展示如何使用 MessageChannel 在主线程和 Worker 之间实现双向通信。

index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Web Worker - MessageChannel 示例</title>
</head>
<body><h1>Web Worker - MessageChannel 示例</h1><button id="sendMessage">发送消息</button><div id="output"></div><script>const worker = new Worker('worker.js');const channel = new MessageChannel();// 发送端口给 Workerworker.postMessage({ port: channel.port1 }, [channel.port1]);// 监听 Worker 的消息channel.port2.onmessage = function(event) {document.getElementById('output').innerHTML = `收到 Worker 的消息: ${event.data}`;};// 发送消息给 Workerdocument.getElementById('sendMessage').addEventListener('click', function() {channel.port2.postMessage('Hello Worker');});</script>
</body>
</html>

worker.js

javascript">self.onmessage = function(event) {const port = event.ports[0];// 监听主线程的消息port.onmessage = function(event) {console.log('收到主线程的消息:', event.data);port.postMessage('Hello Main Thread');};
};

Web Worker 提供了丰富的 API 和方法,包括:

  • 基本通信postMessage 和 onmessage

  • 错误处理onerror 和 onmessageerror

  • 双向通信MessageChannel

  • 广播通信BroadcastChannel

  • 共享内存SharedArrayBuffer 和 Atomics

5.web Work语法示例

主线程

javascript">const worker = new Worker('worker.js');worker.postMessage('Start');worker.onmessage = function(event) {console.log('From Worker:', event.data);
};

Worker 脚本 (worker.js)

javascript">self.onmessage = function(event) {console.log('From Main:', event.data);self.postMessage('Working...');
};

6.注意事项

  1. 无 DOM 访问:Worker 不能直接操作 DOM。

  2. 同源限制Worker 脚本必须与主线程同源。★ 重要

  3. 有限 API:Worker 只能使用部分浏览器 API,如 XMLHttpRequestfetch 等

7.复杂计算阻塞主线程示例 ★ 反例

  • 主线程阻塞

    • JavaScript 是单线程的,复杂计算任务会占用主线程,导致页面无法响应用户交互。

    • 即使是一些简单的操作(如点击按钮、播放动画)也会被阻塞。

  • 用户体验差

    • 页面卡顿会让用户感到不流畅,甚至误以为页面崩溃。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>复杂计算阻塞主线程示例</title><style>body {font-family: Arial, sans-serif;margin: 20px;}#output {margin-top: 20px;padding: 10px;border: 1px solid #ccc;background-color: #f9f9f9;}button {padding: 10px 20px;font-size: 16px;cursor: pointer;margin-right: 10px;}#animation {width: 100px;height: 100px;background-color: red;margin-top: 20px;animation: rotate 2s linear infinite;}@keyframes rotate {from { transform: rotate(0deg); }to { transform: rotate(360deg); }}</style>
</head>
<body>
<h1>复杂计算阻塞主线程示例</h1>
<p>点击“开始计算”按钮,复杂计算任务将在主线程中运行,阻塞页面交互。</p>
<button id="startCalculation">开始计算</button>
<button id="interactiveButton">测试交互</button>
<div id="output"></div>
<div id="animation"></div><script>// 复杂计算任务:计算斐波那契数列(递归实现,性能较差)function fibonacci(n) {if (n <= 1) return n;return fibonacci(n - 1) + fibonacci(n - 2);}// 启动计算document.getElementById('startCalculation').addEventListener('click', function() {document.getElementById('output').innerHTML = "计算中...";const startTime = Date.now();const result = fibonacci(45); // 计算斐波那契数列的第 45 项const endTime = Date.now();document.getElementById('output').innerHTML = `计算完成,结果是: ${result},耗时: ${endTime - startTime} 毫秒`;});// 测试页面交互document.getElementById('interactiveButton').addEventListener('click', function() {alert('页面交互正常!');});
</script>
</body>
</html>

实际效果展示,差点给我电脑干崩啦 

8.Web Worker 不阻塞交互示例 ★ 案例

  1. 复杂计算任务在 Web Worker 中运行,不会阻塞主线程。

  2. 在计算过程中,用户可以正常点击按钮、观看动画,页面交互流畅。

  3. 计算完成后,结果会显示在页面上。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Web Worker 示例 - 不阻塞交互</title><style>body {font-family: Arial, sans-serif;margin: 20px;}#output {margin-top: 20px;padding: 10px;border: 1px solid #ccc;background-color: #f9f9f9;}button {padding: 10px 20px;font-size: 16px;cursor: pointer;margin-right: 10px;}#animation {width: 100px;height: 100px;background-color: red;margin-top: 20px;animation: rotate 2s linear infinite;}@keyframes rotate {from { transform: rotate(0deg); }to { transform: rotate(360deg); }}</style>
</head>
<body>
<h1>Web Worker 示例 - 不阻塞交互</h1>
<p>点击“开始计算”按钮,Web Worker 会在后台执行复杂计算,同时页面交互(如按钮点击和动画)不会受影响。</p>
<button id="startWorker">开始计算</button>
<button id="interactiveButton">测试交互</button>
<div id="output"></div>
<div id="animation"></div><script>// 主线程代码let worker;// 启动 Workerdocument.getElementById('startWorker').addEventListener('click', function() {if (typeof(Worker) !== "undefined") {if (!worker) {worker = new Worker('worker.js'); // 创建 Web Worker}// 监听 Worker 发送的消息worker.onmessage = function(event) {document.getElementById('output').innerHTML = "计算完成,结果是: " + event.data;};// 向 Worker 发送消息,开始计算worker.postMessage('开始计算');document.getElementById('output').innerHTML = "计算中...";} else {document.getElementById('output').innerHTML = "抱歉,你的浏览器不支持 Web Worker。";}});// 测试页面交互document.getElementById('interactiveButton').addEventListener('click', function() {alert('页面交互正常!');});
</script><!-- Web Worker 脚本 -->
<script id="workerScript" type="javascript/worker">// Worker 脚本self.onmessage = function(event) {if (event.data === '开始计算') {// 模拟复杂计算(计算从 0 到 10 亿的和)let result = 0;for (let i = 0; i < 1000000000; i++) {result += i;}self.postMessage(result); // 将结果发送回主线程}};
</script><script>// 将 Worker 脚本转换为 Blob URLconst blob = new Blob([document.getElementById('workerScript').textContent], { type: 'text/javascript' });const workerUrl = URL.createObjectURL(blob);// 替换 worker.js 的路径为 Blob URLwindow.Worker = class extends Worker {constructor() {super(workerUrl);}};
</script>
</body>
</html>

实际效果展示,非常的流畅 

 

9.拓展:为什么要用 Blob URL?★ 了解

  1. 无需外部文件

    • 通常情况下,Web Worker 需要从一个单独的 JavaScript 文件(如 worker.js)加载脚本。

    • 使用 Blob URL 可以将 Worker 脚本直接嵌入到 HTML 文件中,避免创建额外的文件。

  2. 方便演示和分享

    • 在示例代码或教程中,使用 Blob URL 可以让代码更简洁,用户只需复制一个 HTML 文件即可运行完整的功能。

    • 不需要用户额外下载或创建 worker.js 文件。

  3. 动态生成脚本

    • Blob URL 允许动态生成 Worker 脚本内容。例如,可以根据用户输入或其他条件动态修改 Worker 的逻辑。

  4. 同源策略

    • Web Worker 的脚本文件必须与主页面同源(即相同的协议、域名和端口)。使用 Blob URL 可以避免跨域问题,因为 Blob URL 被视为同源。


10.拓展:Blob URL 的工作原理 ★ 了解

  1. Blob

    • Blob 是一个表示二进制数据的对象。我们可以将 JavaScript 代码作为字符串传递给 Blob,生成一个脚本文件。

  2. URL.createObjectURL

    • URL.createObjectURL 方法可以将 Blob 对象转换为一个临时 URL。这个 URL 可以作为 Web Worker 的脚本路径。

  3. 动态加载

    • 通过将 Worker 脚本嵌入 HTML 中的 <script> 标签,然后提取其内容并转换为 Blob URL,最终动态加载 Worker 脚本。

Blob URL代码解析 ★ 了解

以下是代码中与 Blob URL 相关的部分:

javascript"><!-- Web Worker 脚本 -->
<script id="workerScript" type="javascript/worker">// Worker 脚本self.onmessage = function(event) {if (event.data === '开始计算') {// 模拟复杂计算(计算从 0 到 10 亿的和)let result = 0;for (let i = 0; i < 1000000000; i++) {result += i;}self.postMessage(result); // 将结果发送回主线程}};
</script><script>// 将 Worker 脚本转换为 Blob URLconst blob = new Blob([document.getElementById('workerScript').textContent], { type: 'text/javascript' });const workerUrl = URL.createObjectURL(blob);// 替换 worker.js 的路径为 Blob URLwindow.Worker = class extends Worker {constructor() {super(workerUrl);}};
</script>
  1. 嵌入 Worker 脚本

    • 使用 <script id="workerScript" type="javascript/worker"> 将 Worker 脚本嵌入 HTML 中。

    • type="javascript/worker" 是为了避免浏览器直接执行这段脚本。

  2. 提取脚本内容

    • 通过 document.getElementById('workerScript').textContent 获取嵌入的脚本内容。

  3. 生成 Blob URL

    • 将脚本内容传递给 Blob 构造函数,生成一个 Blob 对象。

    • 使用 URL.createObjectURL(blob) 将 Blob 对象转换为一个临时 URL。

  4. 动态加载 Worker

    • 通过重写 window.Worker,将 Blob URL 作为 Worker 的脚本路径。


优点

  • 简化代码结构:无需额外文件,所有代码都在一个 HTML 文件中。

  • 方便测试和演示:用户只需复制一个文件即可运行。

  • 动态生成脚本:可以根据需要动态修改 Worker 的逻辑。


缺点

  • 临时 URL:Blob URL 是临时的,页面刷新后会失效。

  • 不适合大型项目:对于大型项目,建议将 Worker 脚本放在单独的文件中,便于维护和调试。

11.最佳实战写法  ★ 非常重要

在实际工作中,我们通常会使用单独的 worker.js 文件来编写 Web Worker 的逻辑,而不是使用 Blob URL。这样可以更好地组织代码,便于维护和调试

以下是一个完整的示例,展示如何使用单独的 worker.js 文件,并在其中执行复杂的计算任务(如矩阵乘法),然后将结果返回给主线程。同时,页面中会有一些交互(如按钮点击和动画),以展示 Web Worker 如何避免阻塞主线程

/project├── index.html├── worker.js

 1. index.html 文件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Web Worker 示例 - 实际工作场景</title><style>body {font-family: Arial, sans-serif;margin: 20px;}#output {margin-top: 20px;padding: 10px;border: 1px solid #ccc;background-color: #f9f9f9;}button {padding: 10px 20px;font-size: 16px;cursor: pointer;margin-right: 10px;}#animation {width: 100px;height: 100px;background-color: red;margin-top: 20px;animation: rotate 2s linear infinite;}@keyframes rotate {from { transform: rotate(0deg); }to { transform: rotate(360deg); }}</style>
</head>
<body><h1>Web Worker 示例 - 实际工作场景</h1><p>点击“开始计算”按钮,复杂计算任务将在 Web Worker 中运行,页面交互不受影响。</p><button id="startWorker">开始计算</button><button id="interactiveButton">测试交互</button><div id="output"></div><div id="animation"></div><script>// 主线程代码let worker;// 启动 Workerdocument.getElementById('startWorker').addEventListener('click', function() {if (typeof(Worker) !== "undefined") {if (!worker) {worker = new Worker('worker.js'); // 创建 Web Worker}// 监听 Worker 发送的消息worker.onmessage = function(event) {const { result, time } = event.data;document.getElementById('output').innerHTML = `计算完成,结果是: ${result},耗时: ${time} 毫秒`;};// 向 Worker 发送消息,开始计算worker.postMessage({ action: 'start' });document.getElementById('output').innerHTML = "计算中...";} else {document.getElementById('output').innerHTML = "抱歉,你的浏览器不支持 Web Worker。";}});// 测试页面交互document.getElementById('interactiveButton').addEventListener('click', function() {alert('页面交互正常!');});// 终止 Worker(可选)window.addEventListener('beforeunload', function() {if (worker) {worker.terminate(); // 关闭 Worker}});</script>
</body>
</html>

2. worker.js 文件

javascript">// Worker 脚本
function matrixMultiplication(size) {// 创建两个随机矩阵const matrixA = Array.from({ length: size }, () =>Array.from({ length: size }, () => Math.random() * 100));const matrixB = Array.from({ length: size }, () =>Array.from({ length: size }, () => Math.random() * 100));// 结果矩阵const result = Array.from({ length: size }, () =>Array.from({ length: size }, () => 0));// 矩阵乘法for (let i = 0; i < size; i++) {for (let j = 0; j < size; j++) {for (let k = 0; k < size; k++) {result[i][j] += matrixA[i][k] * matrixB[k][j];}}}return result;
}self.onmessage = function(event) {if (event.data.action === 'start') {const startTime = Date.now();const result = matrixMultiplication(200); // 计算 200x200 矩阵乘法const endTime = Date.now();self.postMessage({ result: '矩阵乘法完成', time: endTime - startTime }); // 将结果发送回主线程}
};

效果展示

代码说明

  1. 复杂计算任务

    • 在 worker.js 中,模拟了一个复杂的计算任务:矩阵乘法

    • 创建两个 200x200 的随机矩阵,并计算它们的乘积。这个任务非常耗时,可以明显感受到性能差异。

  2. 主线程与 Worker 通信

    • 主线程通过 worker.postMessage 向 Worker 发送消息,启动计算任务。

    • Worker 通过 self.postMessage 将计算结果返回给主线程。

  3. 页面交互

    • 点击“测试交互”按钮时,会弹出一个提示框。

    • 页面中还有一个红色方块的旋转动画,用于直观展示页面是否卡顿。

  4. 终止 Worker

    • 在页面关闭时,通过 worker.terminate() 终止 Worker,释放资源。


运行效果

  1. 打开浏览器,运行 index.html

  2. 点击“开始计算”按钮,Web Worker 会在后台执行矩阵乘法任务。

  3. 在计算过程中:

    • 点击“测试交互”按钮,页面会立即弹出提示框,证明页面交互未被阻塞。

    • 红色方块的旋转动画会一直流畅运行,证明主线程未被阻塞。

  4. 计算完成后,结果会显示在页面上。


总结

  • 实际工作场景:使用单独的 worker.js 文件编写 Web Worker 逻辑,便于维护和调试。

  • 复杂计算任务:通过矩阵乘法模拟复杂计算任务,展示 Web Worker 的性能优势。

  • 返回数据给主线程:Worker 可以通过 postMessage 将计算结果返回给主线程。


http://www.ppmy.cn/news/1582623.html

相关文章

行业分析---小米汽车2024全年财报

1 背景 其实&#xff0c;关于小米汽车&#xff0c;笔者之前已经多次介绍过了&#xff0c;包括小米汽车成功的原因、智驾进展以及雷军个人的魅力&#xff0c;见博客《自动驾驶---小米汽车智驾进展》和《微自传系列---雷军》。 小米汽车取得的成绩&#xff0c;出乎很多人的意料&a…

燃气场站的岗位与职责

在燃气行业&#xff0c;场站管理需要根据运营规模和安全要求设置多个岗位&#xff0c;形成层级明确、协作紧密的组织架构。以下是基于最新行业规范和管理实践的核心岗位及其职责解析&#xff0c;并梳理了岗位间的逻辑关系&#xff1a; 一、核心岗位及职责 站长 • 主要职责&…

机器学习和深度学习中参数概览

目录 1. 模型参数&#xff08;Learnable Parameters&#xff09; 1.1 神经网络中的核心参数 1.2 归一化层参数 1.3 其他特殊参数 2. 超参数&#xff08;Hyperparameters&#xff09; 2.1 优化相关超参数 2.2 训练流程超参数 2.3 模型结构超参数 2.4 正则化超参数 3. …

屏幕刷新机制(一):机制

屏幕刷新机制&#xff08;一&#xff09;&#xff1a;机制 屏幕刷新机制&#xff08;二&#xff09;&#xff1a;Choreographer、SurfaceFlinger 综述 屏幕整体刷新机制&#xff1a;就是通过Choreographer、SurfaceFlinger&#xff0c;以垂直同步技术(VSYNC)加三重缓冲技术(T…

第六章 | Solidity 函数与可见性修饰符全面讲解

&#x1f4da; 第六章 | Solidity 函数与可见性修饰符全面讲解 ——写对函数&#xff0c;合约安全又高效&#xff01; ✅ 本章导读 智能合约中&#xff0c;函数 是一切交互的入口。 不管你是发币、发 NFT、做 DeFi&#xff0c;所有的链上操作都要通过函数来完成。 而 Solidi…

算法每日一练 (18)

&#x1f4a2;欢迎来到张胤尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 算法每日一练 (18)删除并获得点数题目描述解题思路解题…

【Keil5-开发技巧】

Keil5-开发技巧 ■ Keil5利用AStyle插件格式化代码第一步:下载AStyle插件第二步:添加AStyle插件第三步:AStyle插件介绍■ 一键转UTF-8编码■ Keil5利用AStyle插件格式化代码 第一步:下载AStyle插件 AStyle下载 第二步:添加AStyle插件 解压后 astyle-3.6.7-x64 在重命…

python 实现一个简单的window 任务管理器

import tkinter as tk from tkinter import ttk import psutil# 运行此代码前&#xff0c;请确保已经安装了 psutil 库&#xff0c;可以使用 pip install psutil 进行安装。 # 由于获取进程信息可能会受到权限限制&#xff0c;某些进程的信息可能无法获取&#xff0c;代码中已经…