# Web Worker简介

# 单线程的 JavaScript

JavaScript 是作为浏览器脚本语言而设计的,主要用于与用户交互、操作 DOM,所以在诞生之初,就被设计为了单线程语言

最初的 JavaScript 主要是用于操作页面上的 DOM,如果设计为了多线程语言,那么同时可能有多处地方在执行 JS 代码修改 DOM。一旦这些代码修改了同一处 DOM,那么就会导致冲突。

此外,浏览器的 JS 引擎是与 UI 渲染线程互斥的。当 JS 引擎执行任务时,UI 渲染引擎会被挂起。这是因为 UI 渲染引擎执行的过程中,如果执行 JS 代码,那么可能会导致 DOM 元素被修改,从而进一步导致渲染结果和预期不一致。

单线程语言虽然较为安全,避免了多线程竞争条件下的各种问题,但也导致了一系列问题:

  1. 性能瓶颈:单线程语言意味着 JavaScript 在同一时间只能执行一个任务,这在需要大量计算或者并发任务的场景中,就显得非常低效了
  2. 阻塞任务:一旦有一个任务被阻塞,或是处理时间较长,那么所以其他的任务都会被阻塞,得不到有效的处理
  3. 无法充分利用多核 CPU 的性能:现代计算机通常有多个 CPU 核心,单线程无法充分利用这些资源,从而导致资源浪费。

为了提高 web 应用的响应速度和用户体验,于是我们引入了 Web Worker 的概念

# 什么是 Web Worker

Web Worker 使得在一个独立于 Web 应用程序主执行线程的后台线程中运行脚本操作成为可能。这样做的好处是可以在独立线程中执行费时的处理任务,使主线程(通常是 UI 线程)的运行不会被阻塞/放慢

Web Worker API - Web API | MDN (mozilla.org) (opens new window)

Web Worker 是浏览器的内置线程,允许开发者在浏览器后台运行 JavaScript 代码。创建 Web Worker 时,js 引擎向浏览器申请一个子线程, 子线程完全由浏览器控制,它完全独立于主线程,用于自己独立的内存和全局变量,所以可以与主线程并行执行,互不干扰。

但是,正如我们前面提到的:我们不希望同时有两处代码在修改 DOM。所以,Web Worker 中是无法直接访问到 DOM 的,请勿将操作 DOM 相关的代码交由 Web Worker 执行。Web Worker 更适合去执行一些耗时的复杂任务,如大量计算等。

# Web Worker 的类型

Dedicated Worker:专用 Worker。该 Worker 只能被创建它的脚本使用

Shared Worker:共享 Worker。可以同时被多个标签页使用,但是这些标签页必须同源

Service Worker:服务 Worker。几乎都是用于充当浏览器与服务器之间的代理服务器,实现请求的拦截。在离线场景中得到了广泛的运用。

# 与主线程的通信

主线程和Worker线程之间通过postMessageonmessage事件进行通信。主线程可以向Worker发送消息,Worker处理后再将结果返回给主线程。

Shared Worker 与其他 Worker 的通信方式略有差别,需要通过活动端口 port 进行通信。以下是具体步骤:

  1. 创建Shared Worker:在主线程中创建Shared Worker实例。
  2. 获取MessagePort:通过SharedWorker.port属性获取MessagePort对象。
  3. 启动端口:使用port.start()方法启动端口(如果使用addEventListener监听消息)。
  4. 发送消息:使用port.postMessage()方法发送消息。
  5. 接收消息:通过port.onmessage事件处理接收到的消息。

# 安全与限制

  1. 同源:Web Worker 必须和调用它的页面同源,以确保安全,防止跨站脚本攻击
  2. 不可访问 DOM:Worker线程无法直接访问DOM。不能在Worker中直接操作页面元素,但可以通过消息传递与主线程协作。
  3. 浏览器API受限:Worker线程中不能使用某些浏览器API,如alertconfirmlocalStorage

# 应用场景

  1. 跨标签页通信:使用 Shared Worker 或 Service Worker 可以实现跨标签页的消息传递,详情可参考:跨标签页通信的方案与实践(上) | Happyblog (opens new window)
  2. 大量数据计算:对于大量数据的计算,在主线程中进行会导致页面卡顿,后续任务被阻塞。此时可以考虑使用 Web Worker 进行计算,主线程继续执行后续任务。待 Web Worker 执行完毕之后,再使用 postMessage 通知主线程。
  3. 网络请求 :对于某些耗时较长的网络请求,可以在 Web Worker 中进行,避免阻塞主线程。
  4. 接口缓存:对于数据量较大,且数据变化不频繁的接口,可以使用 Service Worker 进行接口缓存。拦截网络请求,不发送给服务器。
  5. 离线缓存:对于类似聊天类软件,在离线时依然要展示聊天记录之类的大量数据,可以考虑使用 Service Worker 配合 IndexDB 进行。