什么是 Web Worker 以及如何利用它们来优化前端性能

大家好,Vinyl 又来了!👋

欢迎回到我的博客。我知道已经有一段时间了,但我很高兴分享我今年参与的一个项目的一些最新发现和学习成果——一个用于起草、测试和试验智能法律合同和文件的模板游乐场。今天,我们将深入探讨 **Web Workers**:它们是什么、它们如何工作以及如何使用它们来增强您的前端项目。

好吧,让我们想象一下,您在酒吧里喝啤酒,调酒师(您的主线程)必须同时接受订单、准备订单并清理柜台。如果他们忙于酿造复杂的订单(计算量大),排队的其他人就必须等待——令人沮丧,对吧?现在想象一下,调酒师有一名助手(Web Worker),负责在后台清洁和整理品脱杯,而调酒师则专注于接受和下订单。这种团队合作可确保操作更顺畅。

这只是简短的概述。我知道您可能会从描述中想到 API,哈哈,不,它们不是!让我们直接开始吧。

什么是 Web Worker?

Web 开发中的 Web Workers 就像那个助手。它们在后台处理繁重的任务,释放主线程以保持应用程序的响应和流畅。在本文中,我们将深入研究 Web Workers,探索其关键功能,解释如何导航它们,并使用三个真实场景来展示它们在前端开发中的强大功能。我还将提供在其他框架(如 Vue)中使用 Web Workers 的技巧,因为这里的主要用例是 React。

三种类型的 Web Worker

在深入了解如何使用 Web Workers 之前,让我们先了解三种主要类型:

**专用 Worker**:这些 Worker 专用于单个脚本,是最常用的 Worker。它们非常适合执行后台计算或处理某个应用实例的 API 调用等任务。

示例:为特定用户会话压缩数据。

**共享工作者**:这些可以在多个脚本或浏览器选项卡之间共享,使其成为需要跨选项卡通信的任务的理想选择,例如跨选项卡同步数据。

示例:在多个浏览器选项卡之间保持用户会话数据的一致性。

**Service Workers:**与 Dedicated Workers 和 Shared Workers 不同,Service Workers 会拦截网络请求,并充当应用和网络之间的代理。它们通常用于缓存和离线支持。

示例:当用户离线时提供缓存的模板。

您可以在 MDN 的 Web Workers 文档中阅读有关这些类型的更多信息。

Web worker and non web worker flow

要知道使用哪个工人,请考虑你的任务范围:

  • 使用专用工作人员执行孤立的单脚本任务。
  • 使用共享工作者进行多标签通信。
  • 使用 Service Worker 执行与网络相关的任务,例如缓存或离线功能。
  • Web Workers 的主要优势在于它们能够将这些任务从主线程中卸载,从而确保流畅的用户体验。主线程和 Workers 之间的通信通过使用 postMessage 和 onmessage API 的消息系统进行。

    Web Worker 的关键功能

  • onmessage:处理从主线程发送给 worker 的消息。
  • self.onmessage = (event) => {
      console.log('Message received from main thread:', event.data);
    };
  • postMessage:将消息从工作线程发送回主线程。
  • self.postMessage('Task completed');
  • 终止:停止工作者运行。
  • worker.terminate();
  • 错误处理:捕获工作者中的错误。
  • self.onerror = (error) => {
      console.error('Worker error:', error.message);
    };

    其他有用的函数包括用于加载外部脚本的 `importScripts`、用于关闭工作器的 `self.close` 以及用于定时操作的 `setTimeout`/`setInterval`。如有必要,请参阅文档以了解更多详细信息。

    Web Playground 项目中的示例用例

    以下是 Web Workers 可以显著增强示例 Template Playground 项目的三个实际场景:

    案例 1:模板数据的 API 调用

    从 API 获取模板数据会产生巨大的数据集,需要在使用前进行解析。如果直接执行此操作,可能会阻塞 UI 线程。

    **1. 创建 Worker 文件:** 创建 `dataParser.worker.js`。

    // dataParser.worker.js
    self.onmessage = (event) => {
      const { rawData } = event.data;
      const parsedData = rawData.map((template) => ({
        name: template.name,
        tag: template.tag,
      }));
    
      self.postMessage(parsedData);
    };

    **2. 在 React 中使用 Worker:**

    import React, { useState } from 'react';
    
    export default function templateDataParser({ rawData }) {
      const [parsedData, setParsedData] = useState([]);
    
      const parseData = () => {
        const worker = new Worker(new URL('./dataParser.worker.js', import.meta.url));
        worker.postMessage({ rawData });
    
        worker.onmessage = (event) => {
          setParsedData(event.data);
          worker.terminate();
        };
      };
    
      return (
        
    {JSON.stringify(parsedData, null, 2)}
    ); }

    案例2:URL压缩与解压缩

    为了允许用户通过紧凑的 URL 共享他们的模板,Web Workers 可以有效地处理压缩和解压缩。

    **1. 创建 Worker 文件:** 创建 `urlCompressor.worker.js`。

    // urlCompressor.worker.js
    import LZString from 'lz-string';
    
    self.onmessage = (event) => {
      const { action, data } = event.data;
      let result;
    
      if (action === 'compress') {
        result = LZString.compressToEncodedURIComponent(data);
      } else if (action === 'decompress') {
        result = LZString.decompressFromEncodedURIComponent(data);
      }
    
      self.postMessage(result);
    };

    **2. 在 React 中使用 Worker:**

    import React, { useState } from 'react';
    
    export default function URLCompressor({ template }) {
      const [compressedURL, setCompressedURL] = useState('');
    
      const compressTemplate = () => {
        const worker = new Worker(new URL('./urlCompressor.worker.js', import.meta.url));
        worker.postMessage({ action: 'compress', data: template });
    
        worker.onmessage = (event) => {
          setCompressedURL(event.data);
          worker.terminate();
        };
      };
    
      return (
        
    {compressedURL}
    ); }

    案例 3:处理模板的加载动画

    在加载多个模板时,Web Workers 可以异步处理元数据或配置。

    **1. 创建 Worker 文件:** 创建 `templateLoader.worker.js`。

    // templateLoader.worker.js
    self.onmessage = (event) => {
      const { templates } = event.data;
      const loadedTemplates = templates.map((template) => {
        return { ...template, loadedAt: new Date() };
      });
    
      self.postMessage(loadedTemplates);
    };

    **2. 在 React 中使用 Worker:**

    import React, { useState } from 'react';
    
    export default function TemplateLoader({ templates }) {
      const [loadedTemplates, setLoadedTemplates] = useState([]);
    
      const loadTemplates = () => {
        const worker = new Worker(new URL('./templateLoader.worker.js', import.meta.url));
        worker.postMessage({ templates });
    
        worker.onmessage = (event) => {
          setLoadedTemplates(event.data);
          worker.terminate();
        };
      };
    
      return (
        
    {JSON.stringify(loadedTemplates, null, 2)}
    ); }

    以上是 Web Workers 可以改善您工作的三种场景。您可以随意在自己的项目中尝试并试验它们。

    在其他框架中使用 Web Worker 的技巧

    **Vue:**使用 `worker-loader` 插件并在 Vue 组件内调用 worker。

    **Angular**:通过 `ng generate web-worker` 命令利用 Angular 的内置 Web Worker 支持。

    **Svelte**:使用 `vite-plugin-svelte` 加载器无缝导入和使用工作者。

    结论

    Viola,你现在肯定已经走到尽头了!🎉 Web Workers 就像你应用的秘密助手一样,悄悄地处理繁重的工作,而你的主线程则专注于提供出色的用户体验。通过在 URL 压缩、API 调用和数据预处理等场景中使用 Web Workers,你可以显著提高应用的响应能力,让用户体验更流畅。

    所以,不要再等了——立即开始尝试使用 Web Workers,释放 Web 应用程序的全部潜力!下次见!👋

    参考

  • MDN Web Workers API
  • LZ 字符串文档
  • 在 React 中使用 Web Worker
  • GitHub:模板游乐场示例