个人简介
👀个人主页: 前端杂货铺
🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍍前端面试宝典 🎨100个小功能 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒Three.js
🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧
文章目录
Web Worker API
Web Worker 使得在一个独立于 Web 应用程序主执行线程的后台线程中运行脚本操作成为可能。这样做的好处是可以在独立线程中执行费时的处理任务,使主线程(通常是 UI 线程)的运行不会被阻塞/放慢。
Web Worker 概念与用法
Worker 是一个使用构造函数创建的对象(例如 Worker()),它运行一个具名 JavaScript 文件——该文件包含将在 worker 线程中运行的代码。
除了标准的 JavaScript 函数集(如 String、Array、Object、JSON 等),你可以在 worker 线程中运行任何你喜欢的代码。
但有一些例外:
- 不能直接在 worker 线程中操作 DOM 元素。
- 不能使用 window 对象中的某些方法和属性。
数据通过消息系统在 worker 和主线程之间发送——双方都使用 postMessage() 方法发送消息,并通过 onmessage 事件处理程序响应消息(消息包含在 message 事件的 data 属性中)。数据是复制的,而不是共享的。
Worker 全局上下文和函数
worker 在一个与当前 window 不同的全局上下文中运行!虽然 Window 不能直接用于 worker,但许多相同的方法被定义在一个共享的混入(WindowOrWorkerGlobalScope)中,并通过 worker 自己的 WorkerGlobalScope 衍生的上下文提供给它们:
- DedicatedWorkerGlobalScope 用于专用 worker
- SharedWorkerGlobalScope 用于共享 worker
- ServiceWorkerGlobalScope 用于 service worker
所有 worker 和主线程(来自 WindowOrWorkerGlobalScope)共有的一些函数(子集)是:
- WorkerGlobalScope.atob()
- WorkerGlobalScope.btoa()
- WorkerGlobalScope.clearInterval()
- WorkerGlobalScope.clearTimeout()
- WorkerGlobalScope.createImageBitmap()
- WorkerGlobalScope.dump() 非标准
- WorkerGlobalScope.fetch()
- WorkerGlobalScope.queueMicrotask()
- WorkerGlobalScope.reportError()
- WorkerGlobalScope.setInterval()
- WorkerGlobalScope.setTimeout()
- WorkerGlobalScope.structuredClone()
- DedicatedWorkerGlobalScope.requestAnimationFrame()(仅专用 worker)
- DedicatedWorkerGlobalScope.cancelAnimationFrame()(仅专用 worker)
以下函数仅对 worker 可用:
- WorkerGlobalScope.importScripts()(所有 worker)
- DedicatedWorkerGlobalScope.postMessage()(仅限专用 worker)
浏览器兼容性
判断你的浏览器是否支持 Worker。
Web Worker 作用、局限性、适用场景
作用:
- 避免UI阻塞:将耗时任务移到后台线程执行
- 提高性能:利用多核CPU并行处理任务
- 保持响应性:确保用户界面始终流畅响应
局限性:
- 不能直接操作DOM
- 无法访问 window 对象和 document 对象
- 受到同源策略限制
- 通信通过消息传递,数据是复制而非共享
- 不是所有浏览器都支持所有类型的 Worker
适用场景:
- 复杂数学计算(如加密解密、图像处理)
- 大数据集处理与排序
- 高频用户输入处理(如实时搜索)
- 预加载和缓存数据
- 轮询和服务端通信
基于复杂运算分析Web Worker的用途
我们使用斐波那契数列,进行 主线程 VS Worker 分析计算过程中 UI线程 的阻塞程度。
斐波那契数列是指这样一个数列:0,1,1,2,3,5,8,13,21,34,55,89……这个数列从第3项开始 ,每一项都等于前两项之和。
核心代码(index.js
):
// 当 HTML 文档完全解析,且所有延迟脚本(<script defer src="…"> 和 <script type="module">)下载和执行完毕后,会触发 DOMContentLoaded 事件。
document.addEventListener("DOMContentLoaded", function () {
// 获取页面中的所有相关 DOM 元素,便于后续操作
const numberInput = document.getElementById("number");
const numberInputWorker = document.getElementById("number-worker");
const calculateMainBtn = document.getElementById("calculate-main");
const calculateWorkerBtn = document.getElementById("calculate-worker");
const resultMain = document.getElementById("result-main");
const resultWorker = document.getElementById("result-worker");
const timeMain = document.getElementById("time-main");
const timeWorker = document.getElementById("time-worker");
const statusMain = document.getElementById("status-main");
const statusWorker = document.getElementById("status-worker");
const uiStatusMain = document.getElementById("ui-status-main");
const uiStatusWorker = document.getElementById("ui-status-worker");
const barMain = document.getElementById("bar-main");
const barWorker = document.getElementById("bar-worker");
// 斐波那契数列递归实现(低效,仅用于演示计算消耗)
// 输入 n,返回第 n 项斐波那契数
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 初始化 UI 拖动测试,分别用于主线程和 Worker 区域
initDragTest("animation-main", "ui-status-main");
initDragTest("animation-worker", "ui-status-worker");
// 主线程计算按钮点击事件
// 在主线程直接计算斐波那契数,阻塞 UI
calculateMainBtn.addEventListener("click", function () {
// 获取输入值并校验
const n = parseInt(numberInput.value);
if (isNaN(n) || n < 1) {
alert("请输入有效的数字");
return;
}
// 更新主线程区域的状态显示
statusMain.textContent = "状态:计算中...";
resultMain.textContent = "结果:计算中...";
timeMain.textContent = "耗时:--";
uiStatusMain.textContent = "UI响应:检测中...";
// 禁用两个按钮,防止重复点击
calculateMainBtn.disabled = true;
calculateWorkerBtn.disabled = true;
// 记录开始时间
const startTime = performance.now();
// 用 setTimeout 延迟,确保 UI 状态能及时刷新
setTimeout(() => {
try {
// 计算斐波那契数,主线程阻塞
const result = fibonacci(n);
const endTime = performance.now();
const timeTaken = (endTime - startTime).toFixed(2);
// 显示结果和耗时
resultMain.textContent = `结果:斐波那契(${n}) = ${result}`;
timeMain.textContent = `耗时:${timeTaken} 毫秒`;
statusMain.textContent = "状态:计算完成";
// 更新性能对比图表(主线程)
updateChart(parseFloat(timeTaken), 0);
} catch (e) {
// 错误处理
statusMain.textContent = "状态:计算错误";
resultMain.textContent = "结果:计算失败";
} finally {
// 恢复按钮和 UI 状态
calculateMainBtn.disabled = false;
calculateWorkerBtn.disabled = false;
uiStatusMain.textContent = "UI响应:正常";
}
}, 100);
});
// Worker 线程计算按钮点击事件
// 使用 Web Worker 进行斐波那契计算,不阻塞主线程
calculateWorkerBtn.addEventListener("click", function () {
// 获取输入值并校验
const n = parseInt(numberInputWorker.value);
if (isNaN(n) || n < 1) {
alert("请输入有效的数字");
return;
}
// 更新 Worker 区域的状态显示
statusWorker.textContent = "状态:计算中...";
resultWorker.textContent = "结果:计算中...";
timeWorker.textContent = "耗时:--";
uiStatusWorker.textContent = "UI响应:正常";
// 禁用两个按钮,防止重复点击
calculateMainBtn.disabled = true;
calculateWorkerBtn.disabled = true;
// 记录开始时间
const startTime = performance.now();
// 使用独立文件创建 Worker,便于调试
const worker = new Worker("fib-worker.js");
// 或者动态创建 Worker 代码,避免单独文件
// const workerCode = `
// // Worker 内部的斐波那契递归实现
// function fibonacci(n) {
// if (n <= 1) return n;
// return fibonacci(n - 1) + fibonacci(n - 2);
// }
// // 监听主线程消息,计算后返回结果
// self.addEventListener('message', function(e) {
// const n = e.data;
// const result = fibonacci(n);
// self.postMessage(result);
// });
// `;
// // 通过 Blob 创建 Worker 实例
// const blob = new Blob([workerCode], { type: "text/javascript" });
// const worker = new Worker(URL.createObjectURL(blob));
// 向 Worker 发送数据,开始计算
worker.postMessage(n);
// 监听 Worker 返回结果
worker.onmessage = function (e) {
const result = e.data;
const endTime = performance.now();
const timeTaken = (endTime - startTime).toFixed(2);
// 显示结果和耗时
resultWorker.textContent = `结果:斐波那契(${n}) = ${result}`;
timeWorker.textContent = `耗时:${timeTaken} 毫秒`;
statusWorker.textContent = "状态:计算完成";
calculateMainBtn.disabled = false;
calculateWorkerBtn.disabled = false;
// 更新性能对比图表(Worker)
updateChart(0, parseFloat(timeTaken));
// 关闭 Worker
worker.terminate();
};
// Worker 错误处理
worker.onerror = function (e) {
statusWorker.textContent = "状态:Worker错误";
resultWorker.textContent = "结果:计算失败";
calculateMainBtn.disabled = false;
calculateWorkerBtn.disabled = false;
uiStatusWorker.textContent = "UI响应:正常";
};
});
// 拖动测试功能,检测 UI 响应流畅性
// elementId: 可拖动元素的 id
// statusId: 状态显示元素的 id
function initDragTest(elementId, statusId) {
const element = document.getElementById(elementId);
const statusElement = document.getElementById(statusId);
let isDragging = false;
let offsetX, offsetY;
let dragCount = 0;
// 鼠标按下,开始拖动
element.addEventListener("mousedown", function (e) {
isDragging = true;
offsetX = e.clientX - element.getBoundingClientRect().left;
offsetY = e.clientY - element.getBoundingClientRect().top;
element.style.cursor = "grabbing";
element.style.animation = "none";
});
// 鼠标移动,更新元素位置
document.addEventListener("mousemove", function (e) {
if (isDragging) {
dragCount++;
element.style.position = "absolute";
element.style.left = e.pageX - offsetX + "px";
element.style.top = e.pageY - offsetY + "px";
statusElement.textContent = `UI响应:正常 (拖动次数: ${dragCount})`;
}
});
// 鼠标松开,结束拖动
document.addEventListener("mouseup", function () {
if (isDragging) {
isDragging = false;
element.style.cursor = "grab";
element.style.animation = "bounce 1s infinite alternate";
}
});
}
// 更新性能对比图表
// mainTime: 主线程耗时,workerTime: Worker 耗时
function updateChart(mainTime, workerTime) {
const maxHeight = 180; // 图表最大高度(像素)
let maxTime = Math.max(mainTime, workerTime, 1);
// 设置主线程柱状高度
if (mainTime > 0) {
const height = (mainTime / maxTime) * maxHeight;
barMain.style.height = `${height}px`;
}
// 设置 Worker 柱状高度
if (workerTime > 0) {
const height = (workerTime / maxTime) * maxHeight;
barWorker.style.height = `${height}px`;
}
}
// 同步两个输入框的值,保持主线程和 Worker 输入一致
numberInput.addEventListener("input", function () {
numberInputWorker.value = numberInput.value;
});
numberInputWorker.addEventListener("input", function () {
numberInput.value = numberInputWorker.value;
});
});
worker代码(fib-worker.js
)
// fib-worker.js
// 斐波那契递归计算的 Web Worker 脚本,可独立调试
// 斐波那契递归实现
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 监听主线程消息,计算后返回结果
self.addEventListener("message", function (e) {
const n = e.data;
const result = fibonacci(n);
// 发送结果回主线程
self.postMessage(result);
});
通过下面的操作演示我们可以看出来,在不使用worker计算斐波那契数列时无法拖拽小球(UI被阻塞了),但使用worker就可以自由拖拽。
其他代码:
style.css
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
h1 {
color: #2c3e50;
margin-bottom: 10px;
}
.description {
color: #7f8c8d;
max-width: 800px;
margin: 0 auto;
}
.comparison {
display: flex;
gap: 20px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.panel {
flex: 1;
min-width: 300px;
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.panel h2 {
color: #2c3e50;
padding-bottom: 10px;
border-bottom: 2px solid #eee;
margin-bottom: 20px;
}
.input-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #34495e;
}
input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
}
.buttons {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
button {
flex: 1;
padding: 12px;
border: none;
border-radius: 6px;
background: #3498db;
color: white;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
}
button:hover {
background: #2980b9;
transform: translateY(-2px);
}
button:disabled {
background: #95a5a6;
cursor: not-allowed;
transform: none;
}
#calculate-main {
background: #e74c3c;
}
#calculate-main:hover {
background: #c0392b;
}
#calculate-worker {
background: #2ecc71;
}
#calculate-worker:hover {
background: #27ae60;
}
.results {
background: #f8f9fa;
padding: 15px;
border-radius: 6px;
margin-bottom: 20px;
border-left: 4px solid #3498db;
}
.result-item {
margin-bottom: 10px;
}
.ui-test {
background: #e8f4fc;
padding: 20px;
border-radius: 6px;
text-align: center;
}
.animation {
width: 60px;
height: 60px;
background: linear-gradient(45deg, #3498db, #2ecc71);
border-radius: 50%;
margin: 15px auto;
animation: bounce 1s infinite alternate;
cursor: grab;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
@keyframes bounce {
from {
transform: translateY(0) scale(1);
}
to {
transform: translateY(-15px) scale(1.05);
}
}
.performance {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.performance h2 {
color: #2c3e50;
margin-bottom: 20px;
text-align: center;
}
.chart {
display: flex;
height: 200px;
align-items: flex-end;
justify-content: center;
gap: 30px;
padding: 20px;
border-radius: 8px;
background: #f8f9fa;
}
.bar-container {
display: flex;
flex-direction: column;
align-items: center;
}
.bar {
width: 60px;
background: linear-gradient(to top, #3498db, #2ecc71);
border-radius: 4px 4px 0 0;
transition: height 1s;
}
.bar-label {
margin-top: 10px;
font-weight: 600;
}
.explanation {
margin-top: 30px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.explanation h2 {
color: #2c3e50;
margin-bottom: 15px;
}
.points {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}
.point {
flex: 1;
min-width: 250px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
}
.point h3 {
color: #2c3e50;
margin-bottom: 10px;
display: flex;
align-items: center;
}
.point h3::before {
content: "•";
margin-right: 8px;
color: #3498db;
font-size: 24px;
}
@media (max-width: 768px) {
.comparison {
flex-direction: column;
}
}
worker-demo.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Worker 性能分析与比较</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="container">
<header>
<h1>Web Worker 性能分析与比较</h1>
<p class="description">
比较使用Web
Worker与在主线程中执行计算密集型任务的性能差异和用户体验影响
</p>
</header>
<div class="comparison">
<div class="panel">
<h2>不使用 Web Worker</h2>
<div class="input-group">
<label for="number">计算斐波那契数列的第 n 项:</label>
<input type="number" id="number" value="42" min="1" max="50" />
</div>
<div class="buttons">
<button id="calculate-main">在主线程计算</button>
</div>
<div class="results">
<div class="result-item" id="result-main">结果:等待计算...</div>
<div class="result-item" id="time-main">耗时:--</div>
<div class="result-item" id="status-main">状态:就绪</div>
</div>
<div class="ui-test">
<p>UI 响应测试(计算过程中尝试拖动球体):</p>
<div class="animation" id="animation-main" draggable="true"></div>
<p id="ui-status-main">UI响应:正常</p>
</div>
</div>
<div class="panel">
<h2>使用 Web Worker</h2>
<div class="input-group">
<label for="number-worker">计算斐波那契数列的第 n 项:</label>
<input
type="number"
id="number-worker"
value="42"
min="1"
max="50"
/>
</div>
<div class="buttons">
<button id="calculate-worker">使用 Web Worker 计算</button>
</div>
<div class="results">
<div class="result-item" id="result-worker">结果:等待计算...</div>
<div class="result-item" id="time-worker">耗时:--</div>
<div class="result-item" id="status-worker">状态:就绪</div>
</div>
<div class="ui-test">
<p>UI 响应测试(计算过程中尝试拖动球体):</p>
<div class="animation" id="animation-worker" draggable="true"></div>
<p id="ui-status-worker">UI响应:正常</p>
</div>
</div>
</div>
<div class="performance">
<h2>性能比较</h2>
<div class="chart">
<div class="bar-container">
<div class="bar" id="bar-main" style="height: 10px"></div>
<div class="bar-label">主线程</div>
</div>
<div class="bar-container">
<div class="bar" id="bar-worker" style="height: 10px"></div>
<div class="bar-label">Web Worker</div>
</div>
</div>
</div>
<div class="explanation">
<h2>为什么计算时间可能相似?</h2>
<p>
对于纯粹的计算密集型任务,使用Web
Worker可能不会减少总计算时间,甚至可能由于通信开销而稍慢一些。但关键优势在于:
</p>
<div class="points">
<div class="point">
<h3>UI响应性</h3>
<p>
使用Web
Worker时,UI线程不会被阻塞,用户可以继续与页面交互,提供更好的用户体验。
</p>
</div>
<div class="point">
<h3>多核利用</h3>
<p>
Web
Worker允许浏览器利用多核CPU并行处理任务,对于可并行化的工作负载,可以显著减少总计算时间。
</p>
</div>
<div class="point">
<h3>避免页面卡顿</h3>
<p>
在主线程执行长时间任务会导致页面冻结,影响动画、输入响应和用户体验。
</p>
</div>
</div>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>
写在最后
通过使用主线程和web-worker进行的比较,我们直观的认识到worker对于UI线程阻塞的影响程度要远小于主线程。
在处理复杂的数学计算、算法处理等计算密集型任务时,若还要保持UI响应性,那么web-worker确实是一个非常好的选择。
好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!
参考资料:
- 百度 · 百科
- DeepSeek
- MDN · Web Worker API
- VS Code · Copilot