前言
JavaScript 中有一些知识点因为日常开发中不太经常使用,或者在特定场景下才会用到,容易随着时间推移而被容易开发者遗忘。比如说Web Workers
, 对于这类知识点,如何改进学习方式,保证学习效果? 我能想到的方式是对这个知识点的应用场景进行归类总结,整理出每种应用场景下的用法,并以文档的形式输出进行固化。这样后续要是用到时,可以把自己之前总结的用法翻出来看看,快速上手。光有想法是不行的,行动才能对解决问题产生实质性的推动作用。我们就以学习Web Workers
为例,实践一下自己的想法。现在我们进入主题。
创建和使用 Web Workers
JavaScript Web Workers 是一种浏览器提供的 API,允许在后台线程中运行 JavaScript 代码,从而实现异步、并行处理任务,避免阻塞主线程。 Web Workers的工作流程是:
-
创建 Worker : 在主线程中使用
new Worker(url)
构造函数创建一个新的 Worker,其中url
是指向 Worker 脚本文件的路径。例如:jsconst myWorker = new Worker('./worker.js');
-
发送消息 : 主线程通过 Worker 实例的
postMessage()
方法发送数据到 Worker:jsmyWorker.postMessage({ data: 'Hello, Worker!' });
-
接收消息 : 在 Worker 脚本中设置
onmessage
事件处理器来接收并处理主线程发来的消息:jsself.onmessage = function(e) { const data = e.data; console.log(`Received message from main thread: ${data}`); // ... 进行业务处理 ... // 回传处理结果 self.postMessage({ result: 'Processed data.' }); };
-
错误处理 : 主线程和 Worker 都应监听
error
事件,以便捕获并处理潜在的错误:jsmyWorker.onerror = function(e) { console.error('Error occurred in Worker:', e.message); };
在 Worker 脚本中:
jsself.onerror = function(e) { console.error('Error occurred in Worker:', e.message); };
-
关闭 Worker : 当不再需要 Worker 时,可以通过调用其
terminate()
方法来停止并释放 Worker:jsmyWorker.terminate();
Web Workers 典型应用场景
- 计算密集型任务:大规模数据处理
假设有一个包含大量记录的 CSV 文件,需要对其进行排序、统计分析等操作。在主线程中直接处理可能会导致浏览器卡顿。使用 Web Worker,可以创建一个 Worker 线程,将 CSV 数据发送给 Worker,并在 Worker 中进行排序和统计分析。Worker 处理完毕后,通过 postMessage
将结果返回给主线程,主线程再负责更新 UI 展示结果。
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); // 将结果返回给主线程
});
- 长时间运行的任务:大文件解析
假如有一个较大的 JSON 文件需要加载并解析。在主线程中直接进行可能会导致浏览器长时间无响应。可以创建一个 Worker 来异步解析 JSON 文件,解析完成后通知主线程。
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); // 将解析结果返回给主线程
});
- 图像和视频处理:图像处理
假如需要对用户上传的图片应用滤镜效果。在 Worker 线程中处理图像像素数据,避免阻塞主线程。
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); // 将处理后的像素数据返回给主线程
});
- 并行编程:分布式计算
假如要计算一个大整数的质因数分解,可以将整数范围分成多个区间,分别分配给多个 Worker 并行计算,最后主线程汇总结果。
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 收到消息后,将其转发给所有已连接的页面。其他页面接收到更新消息后,即时更新本地的文档视图,从而实现多用户之间的实时协同编辑。
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:
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,但是什么场景可以使用它们,我相信你心里肯定有数了。如果你的业务场景刚好契合上面列举的几种场景,你可以把场景示例代码直接复制到你的项目中使用,这种学习不太常用的知识点的方式,可能更高效。以前看到一篇文章说,中国政府很重视科研,不断加大对科研的投入,以前把大部分科研经费划拨给了高校和科研院所,发现效果却不太理想,许多高校和科研院所是做了一些科研,然而输出成果以论文和专利居多,少有可以直接应用于社会生产实践的科研成果。我们在学习专业技术的过程中,也或多或少存在此种情况,学得了许多知识点,用的时候却不会用。所以要在学习的时候,顺便了解一下应用场景,会学得更扎实。