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

相关推荐
前端郭德纲2 小时前
浅谈React的虚拟DOM
前端·javascript·react.js
2401_879103683 小时前
24.11.10 css
前端·css
ComPDFKit4 小时前
使用 PDF API 合并 PDF 文件
前端·javascript·macos
yqcoder4 小时前
react 中 memo 模块作用
前端·javascript·react.js
优雅永不过时·5 小时前
Three.js 原生 实现 react-three-fiber drei 的 磨砂反射的效果
前端·javascript·react.js·webgl·threejs·three
神夜大侠7 小时前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱7 小时前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js
柯南二号8 小时前
HarmonyOS ArkTS 下拉列表组件
前端·javascript·数据库·harmonyos·arkts
wyy72938 小时前
v-html 富文本中图片使用element-ui image-viewer组件实现预览,并且阻止滚动条
前端·ui·html
前端郭德纲8 小时前
ES6的Iterator 和 for...of 循环
前端·ecmascript·es6