Web Worker ? 网页的工人?究竟是什么?

180 阅读3分钟

前言:当界面卡顿时,Web Worker 来救场

你有没有遇到过这样的场景——点击网页上的某个按钮后,整个页面突然 “冻结” 了,无法滚动、无法点击其他元素,甚至连动画都变得一卡一卡的?这正是JavaScript单线程特性带来的“副作用”:当浏览器忙于执行一段耗时操作(比如大量计算)时,它会阻塞主线程,导致页面失去响应。

想象这样一个页面:一个按钮控制着方块的变色,另一个按钮触发一段从1数到400000000的“疯狂计数”,并在完成后弹出提示。如果没有优化,点击计数按钮后,你会立刻发现——方块变色的动画卡住了,页面像被“冻住”一样,直到计数完成才能恢复。

ezgif.com-video-to-gif-converter (2).gif

这就是Web Worker的用武之地!通过将耗时任务交给后台线程处理,Web Worker能让主线程专注响应用户交互,保持页面流畅。本文将带你用Web Worker,解决“页面卡顿”的痛点。

什么是 Web Worker ?

Web Worker 是 HTML5 提供的浏览器多线程解决方案,允许在后台线程中运行 JavaScript 代码,避免阻塞主线程(UI 线程)。

它经常被用来执行耗时性的代码,避免主线程被阻塞。

拿上面这个例子来说吧:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div class="container">
        <div class="box box1"></div>
        <div class="box box2"></div>
        <div class="box box3"></div>

    </div>

    <div>
        <button id="btn">Change colors</button>
        <button id="count">Alert</button>
    </div>
    <script>
        let boxes = document.querySelectorAll('.box')
        let btn = document.getElementById('btn')
        let isOriginalColor = true  // 获取元素
        ---------------------------------------------------------------
        btn.addEventListener('click', changeColor)  // 来回切换颜色
        function changeColor() {
            boxes.forEach(box => {
                if (isOriginalColor) {
                    box.style.backgroundColor = 'rgb(22,87,172)'
                } else {
                    box.style.backgroundColor = 'rgb(22,172,172)'
                }
            })
            isOriginalColor = !isOriginalColor
        }
      ----------------------------------------------------------------  
        let count = document.getElementById('count'); // 进行计数(耗时性操作)
        count.addEventListener('click', alertCount);
        function alertCount() {
            for (let i = 0; i <= 4000000000; i++) { 
                if (i === 4000000000) {
                    alert(`Reached ${i}`)
                }
            }
        }
        -------------------------------------------------------
    </script>
</body>

</html>

在这个页面中,切换颜色这一行为几乎可以立刻完成,因为它没有那么复杂,也不耗时,但是计数这个操作,已经达到了十亿级别,会特别耗时,比如上面就几乎用了10s......

那么既然知道了计数操作是个耗时操作,而且会阻塞页面的运行,那么能不能把它交给Web Worker?

包能的老弟,接下来看我给你讲!

Web Worker 怎么用 ?

首先我们要创建一个Web Worker(我把HTML中的JS,放入一个main.js文件了):

// main.js

const worker = new Worker('worker.js')

-----------------
// 原来的代码放这里

new Worker()中所填写的是Web Worker的路径,Web Worker本质上就是另一个JS脚本文件,而这个文件被浏览器的另一个线程所执行。

这个时候我们就可以新建一个worker.js文件了。

如何传递信息 ?

Worker的运行是这样的:

image.png

  1. 由主线程的JS给Worker.js发送信息
  2. worker.js 收到信息,开始执行一段逻辑
  3. woker.js 返回结果
  4. main.js 接收结果

主线程发送信息

main.js中需要这样发送信息:

// main.js

const worker = new Worker('worker.js')

worker.postMessage('HI')

worker.postMessage()

worker.postMessage() 方法的标准语法是:

worker.postMessage(message, [transferList]);

message(必填):要传递给 Worker 的数据(支持结构化克隆算法支持的类型)。

transferList(可选):用于转移 ArrayBuffer、MessagePort 等可转移对象的所有权。

当然如果你想传输多条数据,可以封装为一个对象传递过去:

{
    data1:666
    data2:'Good'
    data3:{name:'Skye',age:'20'}
}

但是注意咯,message中不可以传递函数或者Symbol等值。

worker.js 接收信息(onmessage)

它需要接收信息,接收信息后会立即执行一个函数

// worker.js

self.onmessage = function (e) {
// .....
}

self相当于worker.js的全局对象,像window一样。

onmessage像一个监听器,和addEventListener差不多,来监听收到的信息,一旦收到信息,就执行下面的函数。

而接收的形参e,就是一个MessageEvent,它长这样:

image.png

其中的data就是我们从main.js传过来的数据。

这样大家就知道该怎么做了,我们只需要把耗时操作复制过来,让这个JS执行就好了:

self.onmessage = function (e) {
    for (let i = 0; i <= 4000000000; i++) {
        if (i === 4000000000) {
            self.postMessage(i)
        }
    }

}

main.js 接收信息

主线程接收信息也是一样的,用onmessage方法即可:

worker.onmessage = function (e) {
            alert(`Reached ${e.data}`)
        }

这里的形参e,也是一个MessageEvent对象。

这样就完成了。

完整的结构如下

// main.js
let boxes = document.querySelectorAll('.box')
        let btn = document.getElementById('btn')
        let count = document.getElementById('count')
        let isOriginalColor = true
        -------------------------------------------------
        let worker = new Worker('worker.js') // 核心逻辑
        -------------------------------------------------
        btn.addEventListener('click', changeColor)
        function changeColor() {
            boxes.forEach(box => {
                if (isOriginalColor) {
                    box.style.backgroundColor = 'rgb(22,87,172)'
                } else {
                    box.style.backgroundColor = 'rgb(22,172,172)'
                }
            })
            isOriginalColor = !isOriginalColor
        }
        -------------------------------------------------------
        count.addEventListener('click', () => {
            worker.postMessage('Start') // 点击按钮后才发送信息
        })
        worker.onmessage = function (e) {           // 核心逻辑
            console.log(e);

            alert(`Reached ${e.data}`)
        }
        ------------------------------------------------------------
// worker.js


self.onmessage = function (e) {
    console.log(e);

    for (let i = 0; i <= 4000000000; i++) {
        if (i === 4000000000) {
            self.postMessage(i)
        }
    }

}

注意咯,你如果想让文件正确运行,应当启用Live Server。

Web Worker 不能用的API

Web Worker是有限制的,某些API它可以用,但某些不能用:

1. DOM 相关(完全不可用)

API原因
documentWorker 无 DOM 访问权限
windowWorker 的全局对象是 self
element.style无法直接修改 DOM 样式
alert() / confirm()依赖 window,Worker 不能弹窗
localStorage / sessionStorage同步存储,Worker 只能用异步存储(如 IndexedDB)

2. 部分 BOM(浏览器对象模型)API

API替代方案
XMLHttpRequest改用 fetch()(Worker 支持)
location.reload()无法直接操作页面导航
window.open()Worker 不能打开新窗口

3. 其他限制

  • 同步 API(如 fs.readFileSync,Node.js 特有,浏览器 Worker 不涉及)
  • 某些 WebGL 操作(部分高级 WebGLRenderingContext 方法受限)

Web Worker 能用的 API

1. 网络请求

API说明
fetch()发起 HTTP 请求(最常用)
WebSocket建立长连接通信

2. 数据存储(异步)

API说明
IndexedDB浏览器数据库存储
Cache APIService Worker 专用缓存

3. 工具类 API

API说明
setTimeout / setInterval定时器(回调在 Worker 线程执行)
navigator(部分属性)如 navigator.userAgent
console.log()可打印日志(但看不到 UI 控制台)
Blob / File处理二进制数据
WebAssembly运行高性能编译代码

4. 线程通信

API说明
postMessage()与主线程或其他 Worker 通信
BroadcastChannel跨 Worker/Tab 广播消息
SharedArrayBuffer共享内存(需谨慎使用)

总结

OK,这一期就是这样了,让我们来稍微总结一下流程吧!

image.png

这里可能大家有些疑问,比如 “为什么你不在worker中直接操作alert呢?”

这是因为worker的执行环境和我们主线程JS执行环境不一样:

主线程执行全局环境为Window,而worker执行全局环境为self,这就决定了它不能够利用浏览器的一些API,不能操作DOM。