你的浏览器不支持canvas

Love You Ten Thousand Years

Web Workers的前世今生

Date: Author: M/J

本文章采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。转载请注明来自小可嗒嗒的博客

Web Worker 的引入是因为 JavaScript 的单线程问题,使大计算量的问题会造成页面阻塞。为此,HTML 5 制订了 Web Worker 标准,通过创建工作线程来进行计算。

Web Workers 一般说来分为三类:

  • Dedicated workers: 专用 workers,Dedicated workers 只能被创建它的 JavaScript 所调用。它们是专门为某个特定的页面服务的,不能在页面中共享。
  • Shared workers: 共享 workers,Shared worker 可以多个浏览器上下文中调用,所有这些浏览器上下文必须同源(相同的协议,主机和端口号)。
  • Service workers

Dedicated workers

worker 特性检测

为了处理向下兼容,这样是有必要的。

if (window.Worker){
    //do something
}

使用 worker

实例化 Worker 对象,并传入要执行的 JavaScript 文件名。

var worker = new Worker("worker.js");

专用 worker 消息的接收和发送

Worker 是通过 message 和 error 事件与页面通信的。通过 postMessage() 传递数据,该数据保存在 event.data 中。

也就是说,postMessage() 可以进行父子数据传递,message 和 error 事件都可以在父子之间发生。

<!DOCTYPE html>
<html>
<body>

<p>Count numbers: <output id="result"></output></p>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>

<script>
var worker;
function startWorker()
{
if(window.Worker){
  if(typeof(worker)=="undefined"){
    worker = new Worker("worker.js");}
  worker.onmessage = function (event) {
    document.getElementById("result").innerHTML=event.data;
  };
}
else{
  document.getElementById("result").innerHTML="Sorry, your browser does not support Web Workers...";}
}
function stopWorker(){
  worker.terminate();
}
</script>
</body>
</html>

下面是 worker.js 的代码。

var i=0;
function timedCount(){
  i=i+1;
  postMessage(i);
  setTimeout("timedCount()",500);
}
timedCount();

其中,消息内容是任何能够被序列化的值。

终止 worker

从主线程中立即终止一个运行中的 worker,可以调用 worker 的 terminate 方法。

worker.terminate();

在 worker 线程内部,workers 有可以通过自己的 close() 方法进行关闭。

self.close();

self 指向 worker 本身。

引入 subworker

worker 的全局作用域提供一个 importScripts(),该方法接受一个或多个 JavaScript 文件,每个加载过程都是异步的,但执行顺序会按照先后执行。

importScripts('foo.js','bar.js');

值得注意的是,这些脚本是在 worker 的全局作用域中运行。

worker 的全局作用域

Web worker 所执行的代码和当前页面的代码不共享作用域。所以,Web worker 的代码不能访问 DOM,也无法通过任何的方式影响页面的外观。

Web worker 中的全局对象就是 workder 本身。


Shared workers

Shared worker 有自己的全局作用域 SharedWorkerGlobalScope。

生成一个 Shared worker

var sharedWorker = new SharedWorker('sharedWorker.js');

和 Dedicated worker 一个非常大的区别,与 shared worker通信必须通过 port 对象。传递消息前,端口必须被显式打开,打开方式是使用 onmessage 事件处理函数或者 start() 方法。start() 方法的调用只在一种情况下需要,那就是消息事件被 addEventListener() 方法使用。

如果使用 start() 方法打开端口连接时,如果父级线程和 worker 线程需要双向通信,那么它们都需要调用 start() 方法。

sharedWorker.port.start(); // 父级线程中的调用

port.start(); // worker 线程中的调用

共享 worker 中消息的接收和发送

postMessage() 方法必须被端口对象调用。其他都和 Dedicated worker 一致。

例子

比如我们有两个页面 index.htmlindex2.html 分别是计算普通乘法和平方的页面。

<!--index.html 计算普通乘法-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Shared Woker</title>
  </head>
  <body>
    <div class="controls" tabindex="0">
      <form class="" action="index.html" method="post">
        <div class="">
          <label for="number1">乘数1:</label>
          <input type="text" name="" value="0" id="number1">
        </div>
        <div class="">
          <label for="number2">乘数2:</label>
          <input type="text" name="" value="0" id="number2">
        </div>
      </form>
      <p class="result1">结果是:0</p>
      <p>
        <a href="./index2.html" target="_blank">Go to another page</a>
      </p>
    </div>
    <script type="text/javascript" src="./multiply.js"></script>
  </body>
</html>
<!--index2.html 计算平方-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Shared Woker</title>
  </head>
  <body>
    <div class="controls" tabindex="1">
      <form class="" action="index.html" method="post">
        <label for="number3">数字3:</label>
        <input type="text" name="" value="0" id="number3">
      </form>
      <div class="result2">Result: 0</div>
    </div>
    <script type="text/javascript" src="./square.js"></script>
  </body>
</html>

multiply.js 如下:

var first = document.querySelector('#number1');
var second = document.querySelector('#number2');

var result1 = document.querySelector('.result1');

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

  first.onchange = function (){
    myWorker.port.postMessage([first.value, second.value]);
  };
  second.onchange = function (){
    myWorker.port.postMessage([first.value, second.value]);
  };
  myWorker.port.onmessage = function(e){
    result1.textContent = e.data;
  };
}

square.js 如下:

var squreNumber = document.querySelector('#number3');
var result2 = document.querySelector('.result2');

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

  squreNumber.onchange = function(){
    myWorker.port.postMessage([squreNumber.value,squreNumber.value]);
  };
  myWorker.port.onmessage = function(e) {
    result2.textContent = e.data;
  };
}

这两个页面,共享同一个 worker,代码如下:

onconnect = function(e) {
  var port = e.ports[0];

  port.onmessage = function(e){
    var workResult = "Result: " + (e.data[0] * e.data[1]);
    port.postMessage(workResult);
  };
}

参考: Using Web Workers


Service workers

虽然 Service worker 定义为 web worker,但已经不是一个普通的 worker 了。前者如 Dedicated worker 和 Shared worker 主要是进行并行计算,解决耗时的 JS 执行影响 UI 响应的问题。而 Service worker 主要是为了解决 Web app 的用户体验不如 Native app 的普遍问题而提供的一系列的技术的集合。

Service worker 有点像 Shared worker,但是 Service worker 控制多个页面,而不是页面控制 Shared worker。 – 《Service Worker - first draft published》

Service worker 充当 Web app、浏览器和网络代理服务器的角色。基于网络是否可用的情况下,提供良好的离线体验、拦截网络请求和采取合适的行为。 – MDN

有关 Service worker 可参考的内容:


对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。