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

前言

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

创建和使用 Web Workers

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

  1. 创建 Worker : 在主线程中使用 new Worker(url) 构造函数创建一个新的 Worker,其中 url 是指向 Worker 脚本文件的路径。例如:

    js 复制代码
    const myWorker = new Worker('./worker.js');
  2. 发送消息 : 主线程通过 Worker 实例的 postMessage() 方法发送数据到 Worker:

    js 复制代码
    myWorker.postMessage({ data: 'Hello, Worker!' });
  3. 接收消息 : 在 Worker 脚本中设置 onmessage 事件处理器来接收并处理主线程发来的消息:

    js 复制代码
    self.onmessage = function(e) {
      const data = e.data;
      console.log(`Received message from main thread: ${data}`);
      // ... 进行业务处理 ...
      // 回传处理结果
      self.postMessage({ result: 'Processed data.' });
    };
  4. 错误处理 : 主线程和 Worker 都应监听 error 事件,以便捕获并处理潜在的错误:

    js 复制代码
    myWorker.onerror = function(e) {
      console.error('Error occurred in Worker:', e.message);
    };

    在 Worker 脚本中:

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

    js 复制代码
    myWorker.terminate();

Web Workers 典型应用场景

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

假设有一个包含大量记录的 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); // 将结果返回给主线程
    });
  1. 长时间运行的任务:大文件解析

假如有一个较大的 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); // 将解析结果返回给主线程
     });
  1. 图像和视频处理:图像处理

假如需要对用户上传的图片应用滤镜效果。在 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); // 将处理后的像素数据返回给主线程
    });
  1. 并行编程:分布式计算

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

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax