Web Worker

Web Worker

https://www.zoo.team/article/web-worker

b2d2c1d3dd665aa30d7ab98b9d8c586a.png

前言

众所周知,JavaScript 是单线程的语言。当我们面临需要大量计算的场景时(比如视频解码等),UI 线程就会被阻塞,甚至浏览器直接卡死。现在前端遇到大量计算的场景越来越多,为了有更好的体验,HTML5 中提出了 Web Worker 的概念。Web Worker 可以使脚本运行在新的线程中,它们独立于主线程,可以进行大量的计算活动,而不会影响主线程的 UI 渲染。当计算结束之后,它们可以把结果发送给主线程,从而形成了高效、良好的用户体验。Web Worker 是一个统称,具体可以细分为普通的 Worker、SharedWorker 和 ServiceWorker 等,接下来我们一一介绍其使用方法和适合的场景。

普通 Worker
  1. 创建 Worker 通过 new 的方式来生成一个实例,参数为 url 地址,该地址必须和其创建者是同源的。

const worker = new Worker('./worker.js'); // 参数是 url,这个 url 必须与创建者同源
  1. Worker 的方法

  • onmessage 主线程中可以在 Worker 上添加 onmessage 方法,用于监听 Worker 的信息。

    示例:

const worker = new Worker('./worker.js');
worker.onmessage = function (messageEvent) {
 console.log(messageEvent)
}
  • onmessageerror 主线程中可以在 Worker 上添加 onmessageerror 方法,用于监听 Worker 的错误信息。

    示例:

const worker = new Worker('./worker.js');
worker.onmessageerror = function (messageEvent) {
 console.log(messageEvent)
}
  • postMessage() 主线程通过此方法给 Worker 发送消息,发送参数的格式不限(可以是数组、对象、字符串等),可以根据自己的业务选择。

    示例:

const worker = new Worker('./worker.js');
worker.postMessage({ type: 'start', payload: { count: 666 } }); // 发送信息给worker
  • terminate() 主线程通过此方法终止 Worker 的运行。

    示例:

const worker = new Worker('./worker.js');
worker.terminate();
  1. 通信

    Worker 的作用域跟主线程中的 Window 是相互独立的,并且 Worker 中是获取不到 DOM 元素的。所以在 Worker 中你无法使用 Window 变量。取而代之的是可以用 self 来表示全局对象。self 上有哪些方法和属性,感兴趣的小伙伴可以自行输出查看。比较常用的方法是 onmessage、postMessage,主要用来跟主线程进行通信。

    示例:

// 监听事件,主线程可以通过 postMessage 发送信息过来
self.onmessage = (messageEvent) => {
 const { type, payload } = messageEvent.data;
  switch (type) {
    case 'start':
      // 通过 type 去区分不同的业务逻辑,payload 是传过来的数据
      const result = 0;
      // ....,通过一系列处理之后,把最终的结果发送给主线程
      this.postMessage(result);
      break;
  }
};

这里我们从 messageEvent.data 中获取从主线程传递过来的数据。为了业务的扩展性,这边是以 type 去区分不同的业务,payload 承载数据源,通过处理之后把结果发给主线程。主线程的 onmessage 回调函数中就能收到这个结果了。

  1. Worker 中引用其他脚本的方式

跟常用的 JavaScript 一样,Worker 中也是可以引入其他的模块的。但是方式不太一样,是通过 importScripts 来引入。这边我为了演示,新建了一个 constant.js。在 constant.js 定义了一些变量和函数。

示例:

// Worker.js
importScripts('constant.js');
// 下面就可以获取到 constant.js 中的所有变量了

// constant.js
// 可以在 Worker 中使用
const a = 111;

// 不可以在 Worker 中使用,原因未知
const b = function () {
  console.log('test');
};

// 可以在 Worker 中使用
function c() {
  console.log('test');
}
  1. 调试方法

    写代码难免要进行调试。Worker 的调试在浏览器控制台中有专门展示的地方,见下图。

6bc37598fc43787a6fe8d8e9b4a642a5.png
  1. 常见使用场景

  • 一般的视频网站 以优酷为例,当我们开始播放优酷视频的时候,就能看到它会调用 Worker,解码的代码应该写在 Worker 里面。

    5b94e14d4650795953d3b6a54a22a33a.png
  • 需要大量计算的网站 比如 imgcook 这个网站,它能在前端解析 sketch 文件,这部分解析的逻辑就写在 Worker 里。

    f6981a83331edb35acbda609fc565a34.png
SharedWorker

SharedWorker 是一种特定的 Worker。从它的命名就能知道,它是一种共享数据的 Worker。它可以同时被多个浏览器环境访问。这些浏览器环境可以是多个 window, iframes 或者甚至是多个 Worker,只要这些 Workers 处于同一主域。为跨浏览器 tab 共享数据提供了一种解决方案。

  1. 创建 SharedWorker

    创建的方法跟上面普通 Worker 完全一模一样。

const worker = new SharedWorker("./shareWorker.js"); // 参数是 url,这个 url 必须与创建者同源
  1. SharedWorker 的方法

    SharedWorker 的方法都在 port 上,这是它与普通 Worker 不同的地方。

  • port.onmessage

    主线程中可以在 worker 上添加 onmessage 方法,用于监听 SharedWorker 的信息

    示例:

const sharedWorker = new SharedWorker('./shareWorker.js');
sharedWorker.port.onmessage = function (messageEvent) {
  console.log(messageEvent)
}
  • port.postMessage()

    主线程通过此方法给 SharedWorker 发送消息,发送参数的格式不限

    示例:

const sharedWorker = new SharedWorker('./shareWorker.js');
sharedWorker.port.postMessage({ type: 'increase', payload: { count: 666 } });
  • port.start()

    主线程通过此方法开启 SharedWorker 之间的通信

    示例:

const sharedWorker = new SharedWorker('./shareWorker.js');
sharedWorker.port.start()
  • port.close()

    主线程通过此方法关闭 SharedWorker

    示例:

const sharedWorker = new SharedWorker('./shareWorker.js');
sharedWorker.port.close()
  1. 通信

    SharedWorker 跟普通的 Worker 一样,可以用 self 来表示全局对象。不同之处是,它需要等 port 连接成功之后,利用 port 的onmessage、postMessage,来跟主线程进行通信。当你打开多个窗口的时候,SharedWorker 的作用域是公用的,这也是其特点。

    示例:

// index.js
const worker = new SharedWorker('./shareWorker.js');

worker.port.start(); // 开启端口

// 发送信息给 shareWorker
worker.port.postMessage({ type: 'increase', payload: { count: 666 } }); 

// 接受 shareWorker 发过来的数据
worker.port.onmessage = function (val) {
  console.log(val.data)
};

// shareWorker.js
let count = 666;

port.onmessage = (messageEvent) => {
  const { type, payload } = messageEvent.data;

  switch (type) {
    case 'increase':
      port.postMessage(++count);
      break;
    case 'decrease':
      port.postMessage(--count);
      break;
  }
};
  1. Worker 中引用其他脚本

    这个与普通的 Worker 方法一样,使用 importScripts

  2. 调试方法

    在浏览器中查看和调试 SharedWorker 的代码,需要输入 chrome://inspect/

7dd1a56cd2b1165e85c2a48c76623fbf.png
ServiceWorker

ServiceWorker 一般作为 Web 应用程序、浏览器和网络之间的代理服务。他们旨在创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步 API。

  1. 创建 ServiceWorker

// index.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {
    navigator.serviceWorker
      .register('./serviceWorker.js', { scope: '/page/' })
      .then(
      function (registration) {
        console.log('ServiceWorker registration successful with scope: ',
                    registration.scope);
      },
      function (err) {
        console.log('ServiceWorker registration failed: ', err);
      }
    );
  });
}

只要创建了 ServiceWorker,不管这个创建 ServiceWorker 的 html 是否打开,这个 ServiceWorker 是一直存在的。它会代理范围是根据 scope 决定的,如果没有这个参数,则其代理范围是创建目录同级别以及子目录下所有页面的网络请求。代理的范围可以通过 registration.scope 查看。

  1. 安装 ServiceWorker

// serviceWorker.js
const CACHE_NAME = 'cache-v1';
// 需要缓存的文件
const urlsToCache = [
  '/style/main.css',
  '/constant.js',
  '/serviceWorker.html',
  '/page/index.html',
  '/serviceWorker.js',
  '/image/131.png',
];
self.oninstall = (event) => {
  event.waitUntil(
    caches
    .open(CACHE_NAME) // 这返回的是 promise
    .then(function (cache) {
      return cache.addAll(urlsToCache); // 这返回的是 promise
    })
  );
};

在上述代码中,我们可以看到,在 install 事件的回调中,我们打开了名字为 cache-v1 的缓存,它返回的是一个 promise。在打开缓存之后,我们需要把要缓存的文件 add 进去,基本上所有类型的资源都可以进行缓存,例子中缓存了 css、js、html、png。如果所有缓存数据都成功,就表示 ServiceWorker 安装成功;如果控制台提示 Uncaught (in promise) TypeError: Failed to execute 'Cache' on 'addAll': Request failed,则表示安装失败。

  1. 缓存和返回请求

self.onfetch = (event) => {
  event.respondWith(
    caches
    .match(event.request) // 此方法从服务工作线程所创建的任何缓存中查找缓存的结果
    .then(function (response) {
      // response 为匹配到的缓存资源,如果没有匹配到则返回 undefined,需要 fetch 资源
      if (response) {
        return response;
      }
      return fetch(event.request);
    })
  );
};

在 fetch 事件的回调中,我们去匹配 cache 中的资源。如果匹配到,则使用缓存资源;没有匹配到则用 fetch 请求。正因为 ServiceWorker 可以代理网络请求,所以为了安全起见,规范中规定它只能在 https 和 localhost 下才能开启。

  1. 调试方法

    在浏览器中查看和调试 ServiceWorker 的代码,需要输入 chrome://inspect/#service-workers

d5d3e9d5ebf94bb12e7bee5d3477c3d9.png
  1. 演示效果

上面代码中,我缓存了 131.png。切换到离线模式,131 图片还是能显示,134.png 就获取不到了。

7eb8e45668120da9398c6835800a0541.gif

看到这里,大家可能会有疑惑了。这个图片它存到哪里去了?实际上它会把文件自动存到浏览器的 Cache Storage 中。我们打开浏览器可以看到。

576d570933a541160ca5b3b3aafcef47.png
  1. 常见使用场景

    缓存资源文件,加快渲染速度

    这个我们以语雀为例。我们在打开语雀网站的时候,可以看到它使用 ServiceWorker 缓存了很多 css、js 文件,从而达到优化的效果。

d7bdf2dde3a2cbeb1f4780dff4549574.png

总结

类型WorkerSharedWorkerServiceWorker
通信方式postMessageport.postMessage单向通信,通过
addEventListener 监听
serviceWorker 的状态
使用场景适合大量计算的场景适合跨 tab、iframes
之间共享数据
缓存资源、网络优化
兼容性>= IE 10
>= Chrome 4
不支持 IE、Safari、Android、iOS
>= Chrome 4
不支持 IE
>= Chrome 40

本文介绍了 3 种 Worker,他们分别适合不同的场景,总结如上面表格。普通的 Worker 可以在需要大量计算的时候使用,创建新的线程可以降低主线程的计算压力,不会导致 UI 卡顿。SharedWorker 主要是为不同的 window、iframes 之间共享数据提供了另外一个解决方案。ServiceWorker 可以缓存资源,提供离线服务或者是网络优化,加快 Web 应用的开启速度,更多是优化体验方面的。

示例代码:https://github.com/Pulset/Web-Worker

参考文献

  • 在网络应用中添加服务工作线程和离线功能(https://developers.google.com/web/fundamentals/codelabs/offline)

  • Service worker overview(https://developer.chrome.com/docs/workbox/service-worker-overview/)

  • Workers(https://developer.mozilla.org/zh-CN/docs/Web/API/Worker)

  • SharedWorker(https://developer.mozilla.org/zh-CN/docs/Web/API/SharedWorker)

看完两件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我两件小事

1.点个「在看」,让更多人也能看到这篇内容(点了「在看」,bug -1 😊)

2.关注公众号「政采云前端团队」,持续为你推送精选好文

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 60 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com

<think>好的,我现在需要处理用户关于WebWorker的问题。用户想了解如何在JavaScript中使用WebWorker,或者解决常见的相关问题。首先,我得回忆一下WebWorker的基本概念和用法,确保信息准确。 首先,WebWorker允许在后台线程运行脚本,避免阻塞主线程。这点很重要,尤其是在处理复杂计算时。我需要分步骤说明如何创建和使用WebWorker,包括实例化Worker对象、消息传递和终止Worker。然后,可能还要提到同源策略的限制,以及如何应对。 接下来是常见问题及解决方法。比如DOM操作的问题,因为WebWorker不能访问DOM,所以需要提醒用户通过消息传递让主线程处理。然后,错误处理,比如监听error事件。加载外部脚本的问题,可能需要使用importScripts方法。还有兼容性检查,避免在不支持的环境中使用。 然后,性能优化方面,比如避免频繁通信,因为序列化和反序列化数据有开销。内存管理方面,及时终止Worker防止内存泄漏。还要提到共享内存和SharedArrayBuffer,但需要注意线程安全。 最后,生成相关问题,帮助用户进一步学习。比如应用场景、数据通信方法、替代方案等。需要确保问题覆盖用户可能关心的其他方面。 在回答过程中,要遵循系统指令,使用正确的中文,数学公式用$...$,独立公式用$$...$$。不过这个问题可能涉及代码较多,但用户提供的引用是关于Puppeteer的,可能和WebWorker无关,但需要检查是否有必要引用。不过用户给的引用[^1]是关于Chromium命令行参数的,可能不相关,但系统要求引用标识,所以可能需要找到合适的地方插入。不过当前回答可能没有直接引用到相关内容,可能需要忽略或寻找相关点。不过根据用户提供的引用内容,可能无法直接关联到WebWorker,所以可能不需要引用,但系统指令要求引用标识,可能需要调整。或者用户可能误放了引用,需要按实际情况处理。这里可能需要忽略该引用,或者如果有相关内容的话才添加。 检查是否有必要引用用户提供的链接,可能不需要,但根据系统指令,必须在回答中引用的段落末尾添加引用标识。所以需要找到合适的位置添加。比如在提到兼容性时,可能可以提到某些浏览器设置可能需要调整,类似引用中的Chromium参数,但可能比较牵强。或者可能用户提供的引用是示例,实际回答中需要引用其他内容,但用户没有提供其他资料,所以可能无法正确引用。这种情况下,可能需要忽略引用,但系统指令要求必须添加。这里可能需要灵活处理,或者在回答末尾添加引用说明,但用户提供的引用内容是关于Puppeteer的,可能无法关联。可能存在矛盾,需要按照实际情况处理。或许用户希望引用的内容是关于WebWorker的,但实际提供的引用是关于Chromium命令行开关的,可能是个错误。这种情况下,可能需要按照系统指令,在回答中的某个段落末尾添加引用标识,即使内容不相关。例如在提到兼容性检查时,可以添加引用,尽管内容不匹配。或者可能用户提供的引用有误,需要按实际情况处理。或者可能系统示例中的引用是示例,实际回答中需要根据用户提供的引用添加。这里可能需要将引用放在某个相关的位置,比如在讨论WebWorker的兼容性或浏览器设置时,引用提供的链接,说明如何调整浏览器参数,但可能关联性不大。例如,在提到某些浏览器可能需要特定设置才能使用WebWorker,可以添加引用,但用户提供的引用是关于忽略--enable-automation参数的解决方案。这可能不太相关,但根据系统指令,必须添加引用,所以可能需要找到一个相关点。或者可能在讨论WebWorker的调试时,提到使用开发者工具,可能需要引用相关资源,但用户提供的引用是关于Chromium命令行参数的,可能涉及启动参数,比如在调试时可能需要禁用某些安全策略,可以参考该链接中的参数设置。例如,如果WebWorker在本地文件系统中运行时遇到问题(由于同源策略),可能需要通过命令行参数启动浏览器,这时可以引用该链接。因此,在讨论同源策略限制时,可以添加引用,例如提到跨域问题可能需要调整浏览器启动参数,具体可参考相关资源[^1]。这样就能合理插入引用。 综上,回答结构大致如下: 1. WebWorker基本用法:创建、通信、终止。 2. 常见问题及解决方法:DOM操作、错误处理、外部脚本加载、兼容性。 3. 性能优化与内存管理。 4. 相关问题生成。 5. 在讨论同源策略或浏览器设置时,插入引用。</think>### WebWorker的用法与常见问题解决 #### 1. WebWorker基本用法 WebWorker允许在后台线程执行脚本,避免阻塞主线程。使用步骤如下: **a. 创建Worker文件** 新建一个独立的JavaScript文件(如`worker.js`),编写需要在后台运行的代码: ```javascript // worker.js self.onmessage = function(e) { const result = e.data * 2; // 示例:简单计算 self.postMessage(result); }; ``` **b. 主线程中实例化Worker** 在主线程中通过`new Worker()`创建Worker实例,并定义消息接收和错误处理: ```javascript // 主线程代码 const worker = new Worker('worker.js'); // 发送数据到Worker worker.postMessage(10); // 接收Worker返回的结果 worker.onmessage = function(e) { console.log('结果:', e.data); // 输出:20 }; // 错误处理 worker.onerror = function(e) { console.error('Worker错误:', e.message); }; // 终止Worker // worker.terminate(); ``` **注意事项**: - **同源策略**:Worker脚本必须与主线程同源,否则会触发安全错误。若需跨域加载,可通过`Blob`或调整服务器CORS策略实现。 - **作用域限制**:Worker无法访问`DOM`、`window`或`document`对象。 --- #### 2. 常见问题与解决方案 **问题1:Worker中无法操作DOM** **原因**:Worker运行在独立线程,与主线程环境隔离。 **解决**:通过`postMessage`将计算结果传回主线程,由主线程更新DOM。 --- **问题2:Worker脚本加载失败** **错误示例**:`Uncaught DOMException: Failed to construct 'Worker'` **解决**: 1. 检查文件路径是否正确。 2. 确保服务器配置允许加载Worker脚本(如MIME类型为`application/javascript`)。 --- **问题3:数据序列化限制** **示例错误**:传递无法序列化的对象(如函数)会触发`DataCloneError`。 **解决**:仅传递简单数据(如JSON、`ArrayBuffer`)或使用结构化克隆算法支持的类型。 --- **问题4:兼容性问题** **场景**:低版本浏览器(如IE)不支持WebWorker。 **解决**: ```javascript if (typeof Worker !== 'undefined') { // 使用WebWorker } else { // 降级方案:在主线程处理或提示不支持 } ``` --- #### 3. 性能优化与内存管理 - **减少通信频率**:批量传输数据,避免频繁`postMessage`调用。 - **终止闲置Worker**:调用`worker.terminate()`释放资源。 - **使用共享内存**:通过`SharedArrayBuffer`实现高效数据共享(需注意线程安全)。 --- #### 4. 调试技巧 - 在Chrome开发者工具中,通过`Sources > Threads`查看Worker线程。 - 添加`debugger`语句到Worker脚本,断点调试。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值