结合应用场景学习Web Worker, 姿势就正确了

前言

JavaScript 中有一些知识点因为日常开发中不太经常使用,或者在特定场景下才会用到,容易随着时间推移而被容易开发者遗忘。比如说Web Workers, 对于这类知识点,如何改进学习方式,保证学习效果? 我能想到的方式是对这个知识点的应用场景进行归类总结,整理出每种应用场景下的用法,并以文档的形式输出进行固化。这样后续要是用到时,可以把自己之前总结的用法翻出来看看,快速上手。光有想法是不行的,行动才能对解决问题产生实质性的推动作用。我们就以学习Web Workers为例,实践一下自己的想法。现在我们进入主题。

创建和使用 Web Workers

JavaScript Web Workers 是一种浏览器提供的 API,允许在后台线程中运行 JavaScript 代码,从而实现异步、并行处理任务,避免阻塞主线程。 Web Workers的工作流程是:

  1. 创建 Worker: 在主线程中使用 new Worker(url) 构造函数创建一个新的 Worker,其中 url 是指向 Worker 脚本文件的路径。例如:
1
js复制代码const myWorker = new Worker('./worker.js');
  1. 发送消息: 主线程通过 Worker 实例的 postMessage() 方法发送数据到 Worker:
1
js复制代码myWorker.postMessage({ data: 'Hello, Worker!' });
  1. 接收消息: 在 Worker 脚本中设置 onmessage 事件处理器来接收并处理主线程发来的消息:
1
2
3
4
5
6
7
js复制代码self.onmessage = function(e) {
const data = e.data;
console.log(`Received message from main thread: ${data}`);
// ... 进行业务处理 ...
// 回传处理结果
self.postMessage({ result: 'Processed data.' });
};
  1. 错误处理: 主线程和 Worker 都应监听 error 事件,以便捕获并处理潜在的错误:
1
2
3
js复制代码myWorker.onerror = function(e) {
console.error('Error occurred in Worker:', e.message);
};

在 Worker 脚本中:

1
2
3
js复制代码self.onerror = function(e) {
console.error('Error occurred in Worker:', e.message);
};
  1. 关闭 Worker: 当不再需要 Worker 时,可以通过调用其 terminate() 方法来停止并释放 Worker:
1
js复制代码myWorker.terminate();

Web Workers 典型应用场景

  1. 计算密集型任务:大规模数据处理

假设有一个包含大量记录的 CSV 文件,需要对其进行排序、统计分析等操作。在主线程中直接处理可能会导致浏览器卡顿。使用 Web Worker,可以创建一个 Worker 线程,将 CSV 数据发送给 Worker,并在 Worker 中进行排序和统计分析。Worker 处理完毕后,通过 postMessage 将结果返回给主线程,主线程再负责更新 UI 展示结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
js复制代码    // 主线程
const worker = new Worker('dataProcessor.js');
const csvData = ...; // 获取 CSV 数据

worker.postMessage(csvData); // 发送数据到 Worker

worker.addEventListener('message', (event) => {
const result = event.data; // 接收 Worker 返回的结果
updateUI(result); // 更新 UI 展示结果
});

// dataProcessor.js (Worker 线程)
self.addEventListener('message', (event) => {
const csvData = event.data;
const processedData = processData(csvData); // 执行排序、统计等操作

self.postMessage(processedData); // 将结果返回给主线程
});
  1. 长时间运行的任务:大文件解析

假如有一个较大的 JSON 文件需要加载并解析。在主线程中直接进行可能会导致浏览器长时间无响应。可以创建一个 Worker 来异步解析 JSON 文件,解析完成后通知主线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
js复制代码     // 主线程
const worker = new Worker('jsonParser.js');
const jsonUrl = 'largeFile.json';

worker.postMessage(jsonUrl); // 发送文件 URL 到 Worker

worker.addEventListener('message', (event) => {
const parsedData = event.data; // 接收 Worker 返回的解析结果
useParsedData(parsedData); // 使用解析后的数据
});

// jsonParser.js (Worker 线程)
self.addEventListener('message', async (event) => {
const jsonUrl = event.data;
const response = await fetch(jsonUrl);
const jsonData = await response.json();

self.postMessage(jsonData); // 将解析结果返回给主线程
});
  1. 图像和视频处理:图像处理

假如需要对用户上传的图片应用滤镜效果。在 Worker 线程中处理图像像素数据,避免阻塞主线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
js复制代码    // 主线程
const worker = new Worker('imageFilter.js');
const image = document.getElementById('userImage');

const imageData = getImageData(image); // 获取图像像素数据
worker.postMessage({ imageData, filterType: 'grayscale' }); // 发送数据到 Worker

worker.addEventListener('message', (event) => {
const filteredImageData = event.data; // 接收 Worker 返回的处理后像素数据
applyImageDataToCanvas(filteredImageData); // 将处理后的像素数据应用到画布上
});

// imageFilter.js (Worker 线程)
self.addEventListener('message', (event) => {
const { imageData, filterType } = event.data;
const filteredImageData = applyFilter(imageData, filterType); // 应用滤镜效果

self.postMessage(filteredImageData); // 将处理后的像素数据返回给主线程
});
  1. 并行编程:分布式计算

假如要计算一个大整数的质因数分解,可以将整数范围分成多个区间,分别分配给多个 Worker 并行计算,最后主线程汇总结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
js复制代码    // 主线程
const numToFactorize = 12345678901234567890;
const workerCount = navigator.hardwareConcurrency || 4; // 使用可用的核心数

const workers = Array.from({ length: workerCount }, () => new Worker('factorizer.js'));
const intervals = divideRange(numToFactorize, workerCount); // 将整数范围分成多个区间

workers.forEach((worker, index) => {
worker.postMessage(intervals[index]);
worker.addEventListener('message', (event) => {
const factors = event.data; // 接收 Worker 返回的质因数
mergeFactors(factors); // 合并所有 Worker 的结果
});
});

// factorizer.js (Worker 线程)
self.addEventListener('message', (event) => {
const interval = event.data;
const factors = findFactorsInInterval(interval); // 在指定区间内查找质因数

self.postMessage(factors); // 将找到的质因数返回给主线程
});

Shared Workers

除了常规的 Worker,还有一种worker叫 Shared Worker,它可以被多个浏览上下文(如标签页、iframe)共享,实现跨页面通信。Shared Worker 通过 new SharedWorker(url, name) 创建,并使用 port 对象进行通信,而不是直接的 postMessage()

应用场景–实时数据同步

假设有一个多标签页的在线协作编辑应用程序,用户可以在不同的标签页中打开同一份文档进行编辑。为了确保所有参与者都能实时看到其他人的修改,需要在各个页面间实现数据同步。

每个打开文档的标签页都连接到同一个 Shared Worker。当用户在某个页面进行编辑时,更改操作通过 MessagePort 发送给 Shared Worker。Shared Worker 收到消息后,将其转发给所有已连接的页面。其他页面接收到更新消息后,即时更新本地的文档视图,从而实现多用户之间的实时协同编辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
html复制代码<!-- 页面头部引入 sharedWorker.js -->
<script src="sharedWorker.js" type="module"></script>

<script>
// 创建与 Shared Worker 的连接
const worker = new SharedWorker('SharedWorker.js');
const port = worker.port;

// 订阅编辑更新
port.postMessage({ type: 'subscribe' });

// 接收并处理更新消息
port.onmessage = function(event) {
if (event.data.type === 'update') {
// 更新本地文档视图
applyEdit(event.data.content);
}
};

// 当用户在本页面进行编辑时,发送编辑事件到 Shared Worker
function handleUserEdit(newContent) {
port.postMessage({ type: 'edit', content: newContent });
}

</script>

sharedWorker.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
js复制代码// 定义 Shared Worker 逻辑
onconnect = function(e) {
var port = e.ports[0];

// 存储已连接的端口列表,以便广播更新
var connectedPorts = [];

// 当有新的页面连接时
port.onmessage = function(event) {
var message = event.data;

if (message.type === 'subscribe') {
// 添加端口到连接列表
connectedPorts.push(port);
} else if (message.type === 'edit') {
// 接收到编辑操作,广播给所有已连接的页面
broadcastEdit(message.content);
}
};

// 当页面断开连接时
port.onclose = function() {
// 从连接列表中移除该端口
connectedPorts = connectedPorts.filter(p => p !== port);
};

function broadcastEdit(content) {
// 广播编辑事件到所有已连接的页面
connectedPorts.forEach(p => p.postMessage({ type: 'update', content }));
}
};

最后

文中列举了一些web workers和shared worker的应用场景示例程序,我不能打包票说你看完这篇文章,你就能熟练的掌握web workers和shared worker,但是什么场景可以使用它们,我相信你心里肯定有数了。如果你的业务场景刚好契合上面列举的几种场景,你可以把场景示例代码直接复制到你的项目中使用,这种学习不太常用的知识点的方式,可能更高效。以前看到一篇文章说,中国政府很重视科研,不断加大对科研的投入,以前把大部分科研经费划拨给了高校和科研院所,发现效果却不太理想,许多高校和科研院所是做了一些科研,然而输出成果以论文和专利居多,少有可以直接应用于社会生产实践的科研成果。我们在学习专业技术的过程中,也或多或少存在此种情况,学得了许多知识点,用的时候却不会用。所以要在学习的时候,顺便了解一下应用场景,会学得更扎实。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%