• 前端如何使用Web Worker以避免主线程阻塞
  • 发布于 2个月前
  • 149 热度
    0 评论
前几天的一场面试中,被面试官吐槽了一下,我在我项目中直接使用 Prettier 来做代码格式化,首先加载该依赖包就需要一定的成本了,况且格式化的过程中可能需要一些计算,会导致阻塞整个浏览器。

在这里,我个人目前能想到的就两种方案,一种是使用网络请求的方式,也就是将这个问题抛给后端去处理,后端处理的话也是用相同的办法去做,只是最终把结果返回给前端。因为在浏览器的事件循环中,网络请求并不会阻塞 js 主线程。还有一个方法就是使用 web worker,将需要格式化或转换的代码交给 worker 去处理,处理完成之后最终把结果返回。

什么是 Web Worker
Web Worker 是一种在浏览器中运行 JavaScript 代码的机制,它允许您在后台线程中执行一些任务,而不会阻塞主线程。主线程通常用于处理用户界面的交互和渲染,而 Web Worker 可以用于执行一些耗时的计算、网络请求、数据处理等任务,以提高应用的性能和响应速度。它的主要特点是能进行并行计算,他允许在后台同时运行多个线程,这些线程可以并行执行任务。这使得可以同时处理多个耗时的操作,而不会阻塞用户界面。

Web Worker 的使用
首先,您需要创建一个单独的 JavaScript 文件,这将是我们的 Web Worker 文件。例如,我们可以创建一个名为 worker.js 的文件,并编写如下代码:
// worker.js

self.addEventListener("message", (event) => {
  // 在这里执行后台任务
  const data = event.data;
  const result = doSomeHeavyWork(data);
  self.postMessage(result);
});

function doSomeHeavyWork(data) {
  // 执行耗时的计算或任务
  return data * 2;
}
在主线程代码 JavaScript 代码中,我们可以创建一个 Web worker 实例,然后通过消息传递与他进行通信:
// 在主线程中
const worker = new Worker("worker.js");

// 发送消息到 Web Worker
worker.postMessage(42);

// 监听从 Web Worker 返回的消息
worker.addEventListener("message", (event) => {
  console.log("接收到 web worker 返回的信息:", event.data);
  worker.terminate();
});
在上述的代码中,使用 addEventListener 监听 message 事件,然后在事件处理程序中执行后台任务。使用 self.postMessage 向主线程发送结果。在主线程代码中,可以通过使用 postMessage 方法向 Web Worker 发送消息。

最终代码允许结果如下所示:

如何在项目中使用
首先先来说明一下的我自己的情况,我最终在开发一个在线代码协同编辑器,Github 地址 在整个项目中,我们难免会用到一些代码转换或者代码格式化,那么我们这里可以通过使用 Web Worker 的方式来对其进行处理,最终把结果返回给 JavaScript 主线程。

要想使用,我们首先需要 sass 模块:
pnpm add sass
首先我们创建一个 compiler.ts 文件作为 worker 文件,代码如下所示:
import * as sass from "sass";

self.onmessage = (event) => {
  const scssCode = event.data;

  try {
    const result = sass.compileString(scssCode);

    self.postMessage({ compiledCss: result.css });
  } catch (error: any) {
    self.postMessage({ error: error.message });
  }
};
它接收从主线程发送的 SCSS 代码,使用 sass 包进行编译,然后将编译后的 CSS 或错误消息发送回主线程。在我们的 App.tsx 文件中编写以下代码,如下所示:
import React, { useState } from "react";
// 堆代码 duidaima.com
const App = () => {
  const [scssCode, setScssCode] = useState<string>(
    `
    $primary-color: #3498db;
    
    .button {
      background-color: $primary-color;
      color: white;
      padding: 10px 20px;
      border: none;
      cursor: pointer;
      
      &:hover {
        background-color: darken($primary-color, 10%);
      }
    }
    `
  );
  const compileScss = () => {
    if (!scssCode) return;

    const worker = new Worker(new URL("./compiler.ts", import.meta.url), {
      type: "module",
    });
    worker.onmessage = (event) => {
      if (event.data.error) {
        console.error(event.data.error);
      } else {
        setScssCode(event.data.compiledCss);
      }
      worker.terminate();
    };

    worker.postMessage(scssCode);
  };

  return (
    <div>
      <code
        style={{
          background: "black",
          color: "white",
          height: "200px",
          width: "200px",
          display: "block",
        }}
      >
        {scssCode}
      </code>
      <button onClick={compileScss}>编译代码</button>
    </div>
  );
};

export default App;
在这段代码中我主要做的事情是我偷了个懒哈哈哈哈哈。

我把我们要编写的 scss 代码事先编写好了并通过 worker.postMessage(scssCode) 的方式向 web worker 发送 scss 源代码,并使用 worker.onmessage = event => { ... } 来监听 Web Worker 返回的消息。如果返回的消息包含编译后的 CSS,则更新 scssCode 的状态以显示编译后的 CSS。

首先请看原图,这是我们没有编译过的代码,如下图所示:

当我们点击编译按钮,它会显示被编译后的代码,也就是 css 代码:

大功告成,这个方法运用得好,说不定就成为了你项目中的亮点呢。

总结
通过这种实现方式结合了前端技术,为用户提供了一个简单的在线 SCSS 编译工具。通过将耗时的编译操作放在 Web Worker 中执行,保持了用户界面的流畅性,并提供了实时的编译反馈。
用户评论