登录 ×

梨花寨前端技术交流平台!

html,css,js,html5,css3等

HTML5 Web Workers 的用途

2018-07-18 12:27:00 | 浏览量:58 前沿信息

什么是Web Worker?

    当在HTML页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。这样遇到执行耗时长的任务,就影响用户的正常使用了。

    HTML5定义了一种作为后台线程的Web Worker。web worker是一个用来执行计算密集任务而不冻结用户界面的后台线程。很好地解决了上述问题。

    Web Worker是运行在后台的JavaScript,独立于其他脚本,不阻塞脚本响应点击或者其他用户交互.您可以继续做任何愿意做的事情:点击、选取内容等等,还允许执行长期任务,无需页面保持响应,不会影响页面的性能。

    Web Worker 的作用,就是为JavaScript创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行。在主线程运行的同时,Worker线程在后台运行,两者互不干扰。等到Worker线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被Worker线程负担了,主线程(通常负责UI交互)就会很流畅,不会被阻塞或拖慢。

    Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。

一个简单的Demo:

index.html 中的main.js

var worker = new Worker('worker.js');
//主线程采用new命令,调用Worker()构造函数,新建一个Worker线程

worker.postMessage('Hello World');
//主线程调用worker.postMessage()方法,向Worker发消息,它可以是各种数据类型,包括二进制数据。

worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
  doSomething();
}
//主线程通过worker.onmessage指定监听函数,接收子线程发回来的消息,事件对象的data属性可以获取Worker发来的数据。

worker.onerror=function(error){
    console.log(error.filename,error.lineno,error.message);
}
//主线程可以监听Worker是否发生错误。如果发生错误,Worker会触发主线程的error事件。

worker.terminate();
//Worker 完成任务以后,主线程就可以把它关掉。

Worker.onmessageerror:指定messageerror事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。

worker.js内容

self.addEventListener('message', function(e) {
 self.postMessage(e.data);
}, false);

Worker 线程内部需要有一个监听函数,监听message事件。

Worker 内部也可以监听error事件。

self.onmessageerror:指定messageerror事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。

Worker()构造函数的参数是一个脚本文件,该文件就是Worker线程所要执行的任务。由于Worker不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker就会默默地失败。

关闭Worker

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

// 主线程
worker.terminate();

// Worker 线程
self.close();

一般建议采用关闭Worker线程。

Worker 加载脚本

Worker 内部如果要加载其他脚本,有一个专门的方法importScripts()。

importScripts('script1.js');

该方法可以同时加载多个脚本。

importScripts('script1.js', 'script2.js');

Web Worker 有以下几个使用注意点:

(1)同源限制

分配给Worker线程运行的脚本文件,必须与主线程的脚本文件同源。

(2)DOM限制

Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的DOM对象,也无法使用document、window、parent这些对象。但是,Worker线程可以navigator对象和location对象。

(3)通信联系

Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

(4)脚本限制

Worker 线程不能执行alert()方法和confirm()方法,但可以使用XMLHttpRequest对象发出AJAX请求。

(5)文件限制

Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

实例:Worker线程完成轮询

有时,浏览器需要轮询服务器状态,以便第一时间得知状态改变。这个工作可以放在Worker里面。

function createWorker(f) {
  var blob = new Blob(['(' + f.toString() +')()']);
  var url = window.URL.createObjectURL(blob);
  var worker = new Worker(url);
  return worker;
}
var pollingWorker = createWorker(function (e) {
  var cache;
  function compare(new, old) {
   //...
  };
  setInterval(function () {
    fetch('/my-api-endpoint').then(function (res) {
      var data = res.json();
      if (!compare(data, cache)) {
        cache = data;
        self.postMessage(data);
      }
    })
  }, 1000)
});
pollingWorker.onmessage = function () {
  // render data
}
pollingWorker.postMessage('init');

上面代码中,Worker每秒钟轮询一次数据,然后跟缓存做比较。如果不一致,就说明服务端有了新的变化,因此就要通知主线程。

同页面的Web Worker

通常情况下,Worker载入的是一个单独的JavaScript脚本文件,但是也可以载入与主线程在同一个网页的代码。

<!DOCTYPE html>
  <body>
    <script id="worker" type="app/worker">
      addEventListener('message', function () {
        postMessage('some message');
      }, false);
    </script>
  </body>
</html>

上面是一段嵌入网页的脚本,注意必须指定<script>标签的type属性是一个浏览器不认识的值,上例是app/worker。

然后,读取这一段嵌入页面的脚本,用Worker来处理。

var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.onmessage = function (e) {
  // e.data === 'some message'
};

上面代码中,先将嵌入网页的脚本代码,转成一个二进制对象,然后为这个二进制对象生成URL,再让Worker加载这个URL。这样就做到了,主线程和Worker的代码都在同一个网页上面。

数据通信

    前面说过,主线程与Worker之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,Worker对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给Worker,后者再将它还原。

主线程与Worker之间也可以交换二进制数据,比如File、Blob、ArrayBuffer等类型,也可以在线程之间发送。


// 主线程
var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < uInt8Array.length; ++i) {
  uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8,...]
}
worker.postMessage(uInt8Array);
// Worker 线程
self.onmessage = function (e) {
  var uInt8Array = e.data;
  postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());
  postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
};


    但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向Worker发送一个500MB文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects。这使得主线程可以快速把数据交给Worker,对于影像处理、声音处理、3D运算等就非常方便了,不会产生性能负担。

如果要直接转移数据的控制权,就要使用下面的写法。

// Transferable Objects 格式


worker.postMessage(arrayBuffer, [arrayBuffer]);


// 例子


var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);

使用WebWoker处理图片,将图片模糊,如下:

html:


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
img {
      width: 400px;
      height: 300px;
    }
</style>
</head>
<body>
<img src="../images/demo.png" onclick="smear(this)"/>
<img src="../images/1.png" onclick="smear(this)"/>
<img src="../images/2.png" onclick="smear(this)"/>
<script>
    function smear(img){
        var canvas = document.createElement("canvas");
        canvas.width = img.width;
        canvas.height = img.height;
        var context = canvas.getContext("2d");
        context.drawImage(img,0,0);
        var pixels = context.getImageData(0,0,img.width,img.height);     
        var worker = new Worker("www.lihuazhai/com/xx/SmearWorker.js");
        worker.postMessage(pixels);
        worker.onmessage = function(e){
            var smeared_pixels = e.data;
            context.putImageData(smeared_pixels,0,0);
            img.src = canvas.toDataURL();
            worker.terminate();
            canvas.width = canvas.height = 0;
        }
    }
</script>
</body>
</html>

 SmearWorker.js:


/**
 * SmearWorker---smear the picture
 */
 function smear(pixels){
     var data = pixels.data,
         width = pixels.width,
         height = pixels.height;
     var n = 10,
         m = n-1,
         i,
         col;

     for(var row=0; row<height; row++){
         i = row*width*4 + 4;
         for(col =1;col<width; col++,i+=4){
             data[i] = (data[i] +data[i-4]*m/n);
             data[i+1] = (data[i+1] +data[i-3]*m/n);
             data[i+2] = (data[i+2] +data[i-2]*m/n);
             data[i+3] = (data[i+3] +data[i-1]*m/n);
         }
     }
     return pixels;
 }

 onmessage = function(e){
     postMessage(smear(e.data));
 };

worker的用处

    根据worker独立线程这一特性.他的使用场景也非常清晰了.反正什么大规模数据并发,I/O操作的.都可以交给他来进行.总的来说有一下几种场景:

懒加载数据、文本分析、流媒体数据处理、web database的更新、大量JSON返回数据的处理。


WebWorker实际使用方式

    实际开发中我们不会吧所有的代码都放在一个文件中让子线程加载,肯定会选择模块化开发。官方提供的方式是使用importScripts,但是这个在实际开发中很不实用,importScripts的加载方式是阻塞式的,所以我们最好用打包工具将所有worker中需要的文件打包成一个文件。这里我推荐webworkify-webpack,这是webpack的一个插件。

    其他,我们在js中使用XMLHttpRequest时经常会设置为异步方式,因而在主浏览器线程张使用同步很不好,我们可以在worker中使用同步的XMLHttpRequest。

我们应该尽量使用WebWorker处理计算量大的,主要的工作,否则因为WebWorker本身不是轻量级的线程,因而有点得不偿失。

需求及问题提交:

点击图片更换数字

0.6550s