都说前端学不动了 - 来聊聊 Web Worker 吧

3,193 阅读5分钟

背景

为啥突然想学习 Web Worker 了呢,因为我在某金上看到了一篇文章,而那篇文章有个评论说 Web Worker 实现起来很丝滑,我在想是要怎么实现呢,于是就开启了探究 Web Worker 之旅……

阅读 MDN

一般来讲,对于前端领域看到一个陌生又熟悉的词汇,搜索之后都可以看到MDN的解释说明,引经据典嘛,来咯:

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

显然使用 Web Worker 是有好处的,就是可以将任务重的计算在不阻塞主线程的情况下继续运行,实现性能的提升(不过也不宜过度使用,后面会说到)。

Web Worker 可分为 Dedicated Workers、Shared Workers、Service Workers、Chrome Workers以及音频Workers,后面两个我负责的项目中使用场景不多,就不展开了,主要介绍前三个 Web Workers。

Dedicated Workers

定义、使用

Dedicated Workers 使用构造函数 Worker() 创建一个Worker对象,构造函数接受一个 JavaScript文件URL,这个文件包含了将在 worker 线程中运行的代码,具体例子:

// main.js
var myWorker = new Worker('./worker.js');

// worker.js,里面是worker线程运行的任务(执行的计算比较重的代码)
onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}

启动/运行 Web Worker

首先要知道 Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。所以如果本地创建了一个 html 文件和 js 文件,直接在浏览器打开该 html 文件,Web Worker 是无效的,比如我打开的是file:///Users/xxx/Desktop/aaa/aaa.html,内部引入的js包含了上面 Web Worker 相关的 JS 代码,那么控制台会报出一个错误

image

所以如果想在本地调试,需要将文件serve起来,我是用的是python命令:python -m SimpleHTTPServer 8000

Chrome Debug

代码上实现了 Web Worker,也确实能正常运行,那么我怎么知道worker到底在哪里呢,我的页面里面有几个workers呢?

image

打开 Chrome -> CMD+SHIFT+I -> Sources Tab -> Page,然后就可以看到有多少个 Web Worker 以及具体 Web Worker 的脚本。

关闭 Web Worker

使用完毕,为了节省系统资源,必须关闭 Worker。

// 主线程
worker.terminate();
// Worker 线程
self.close();

对于 Dedicated Workers而言,关闭意味着在 chrome 的sources tab也会消失:

web-worker

Shared Workers

Shared Workers 跟 Dedicated Workers使用上是基本一致的,可能更复杂一些,Shared Workers 可被不同的窗体的多个脚本运行,例如IFrames等。代码举例🌰:

// main.js
if (!!window.SharedWorker) {
  var myWorker = new SharedWorker("worker.js");

  first.onchange = function() {
    myWorker.port.postMessage([first.value, second.value]);
    console.log('Message posted to worker');
  }

  second.onchange = function() {
    myWorker.port.postMessage([first.value, second.value]);
    console.log('Message posted to worker');
  }

  myWorker.port.onmessage = function(e) {
    result1.textContent = e.data;
    console.log('Message received from worker');
    console.log(e.lastEventId);
  }
}

// worker.js
onconnect = function(e) {
  var port = e.ports[0];
  port.onmessage = function(e) {
    var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
    port.postMessage(workerResult);
  }
}

在Chrome的debug方式也不一样,打开chrome://inspect,可以看到很多navigations,其中有一个叫做Shared Workers,那这里是可以看到打开的网页有哪些是使用了 Shared Workers的。

image

Service Workers

说实话,我本人接触的第一个Web Workers就是Service Worker,以前有一个项目是给工人在工地上做工时应用,工地也没有网络,或者说信号极差,所以对离线要求也比较高,当时调研过 Service Worker,但是使用起来有点复杂的,再加上当时不满足浏览器的兼容性,所以就使用了另外一种方式indexdb

根据文档,Service Worker 可以创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。

Angular框架是实现了 Service Worker的,而其中也有一些bug(记得是版本7,现在有可能已经修复了,很久没用过Angular了😅),如果开启了之后,可能会对版本的更新有一定的影响,话说至今我都不明白为啥那个项目要开启Service Worker,大家也米有离线的需求额……

在Chrome浏览器也是可以很方便地debug,在Application tab的子菜单里面:

image

开发注意的地方/限制

  1. 前面也提到了,Web Workers 不能通过本地文件的方式运行,只能通过网络,否则无法执行;
  2. 同源:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源;
  3. DOM 限制:可能很多人特别开心,那页面渲染的性能瓶颈是不是就能通过 Web Workers 来解决了呢?Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。

但是还是有很多 Web API 在 Web Workers中是可以访问的,比如使用 XMLHttpRequest 来访问网络,可以使用navigator对象和location对象等,所以别灰心,大多数 API 也是可以用的。

思考

虽然学习了 Web Workers,也知道如何使用,但是目前来讲好像使用 Web Workers 解决问题的项目不多,通过搜索引擎发现很多库/工具都实现了 Web Workers呢,我个人还是很看好 Web Workers 滴。

欢迎大家一起交流哦🍺🍺🍺

References: