UDN-企业互联网技术人气社区

板块导航

浏览  : 1675
回复  : 2

[HTML5] html5之Web Worker -- js多线程编程

[复制链接]
白青青的头像 楼主
发表于 2017-1-3 15:31:17 | 显示全部楼层 |阅读模式
  什么是Web Worker?

  web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验。

  一般来说Javascript和UI页面会共用一个线程,在HTML页面中执行js脚本时,页面的状态是不可响应的,直到脚本已完成。而这段代码可以交给Web Worker在后台运行,那么页面在Javascript运行期间依然可以响应用户操作。后台会启动一个worker线程来执行这段代码,用户可以创建多个worker线程。

  有两种 Web Worker

  Web workers可分为两种类型:专用线程dedicated web worker,以及共享线程shared web worker。 Dedicated web worker随当前页面的关闭而结束;这意味着Dedicated web worker只能被创建它的页面访问。与之相对应的Shared web worker可以被多个页面访问。在Javascript代码中,“Work”类型代表Dedicated web worker,而“SharedWorker”类型代表Shared web worker。

  在绝大多数情况下,使用Dedicated web worker就足够了,因为一般来说在web worker中运行的代码是专为当前页面服务的。而在一些特定情况下,web worker可能运行的是更为普遍性的代码,可以为多个页面服务。在这种情况下,我们会创建一个共享线程的Shared web worker,它可以被与之相关联的多个页面访问,只有当所有关联的的页面都关闭的时候,该Shared web worker才会结束。相对Dedicated web worker,shared web worker稍微复杂些。

  new Worker()对象代表Dedicated Web Worker,以下示例代码都为Dedicated Web Worker。

  如何创建 Web Worker?

  创建一个新的 worker 十分简单。你所要做的就是调用 Worker() 构造函数,指定一个要在 worker 线程内运行的脚本的 URI,如果你希望能够与worker进行通信,接收其传递回来的数据,可以将worker的 onmessage 属性设置成一个特定的事件处理函数,当 web worker 传递消息时,会执行事件监听器中的代码。event.data 中存有来自 workera 的数据。。

  example.html: (主页面):
  1. var myWorker = new Worker("woker_demo.js");

  2. myWorker.onmessage = function (oEvent) {
  3.   console.log("Called back by the worker!\n");
  4. };
复制代码

  或者,也可以使用

  addEventListener()添加事件监听器:
  1. var myWorker = new Worker("woker_demo.js");

  2. myWorker.addEventListener("message", function (oEvent) {
  3.   console.log("Worker said : " + oEvent.data);
  4. }, false);

  5. myWorker.postMessage("hello my worker"); // start the worker.
复制代码

  例子中的第一行创建了一个新的 worker 线程。第三行为 worker 设置了  message 事件的监听函数。当 worker 调用自己的  postMessage() 函数时就会调用这个事件处理函数。最后,第七行启动了 worker 线程。

  注意: 传入 Worker 构造函数的参数 URI 必须遵循  同源策略
  1. worker_demo.js (worker):
  2. postMessage("I\'m working before postMessage(\'hello my worker\').");

  3. onmessage = function (oEvent) {
  4.   postMessage("Hi " + oEvent.data);
  5. };
复制代码

  注意:通常来说,后台线程 – 包括 worker – 无法操作 DOM。 如果后台线程需要修改 DOM,那么它应该将消息发送给它的创建者,让创建者来完成这些操作。

  你可以在前台做一些小规模分布式计算之类的工作,不过Web Worker有以下一些使用限制:

  • Web Worker无法访问DOM节点;
  • Web Worker无法访问全局变量或是全局函数;
  • Web Worker无法访问window、document之类的浏览器全局变量、方法;

  不过Web Worker作用域中依然可以使用有:

  定时器相关方法 setTimeout(),clearTimeout(),setInterval()...之类的函数

  navigator对象,它含有如下能够识别浏览器的字符串,就像在普通脚本中做的那样,如:appName、appVersion、 userAgent ...

  引入脚本与库,Worker 线程能够访问一个全局函数, importScripts() ,该函数允许 worker 将脚本或库引入自己的作用域内。你可以不传入参数,或传入多个脚本的 URI 来引入;以下的例子都是合法的:
  1. importScripts();                        /* 什么都不引入 */
  2. importScripts('foo.js');                /* 只引入 "foo.js" */
  3. importScripts('foo.js', 'bar.js');      /* 引入两个脚本 */
复制代码

  浏览器将列出的脚本加载并运行。每个脚本中的全局对象都能够被 worker 使用。如果脚本无法加载,将抛出  NETWORK_ERROR  异常,接下来的代码也无法执行。而之前执行的代码(包括使用setTimeout延迟执行的代码)却依然能够使用。 importScripts()   之后
的函数声明依然能够使用,因为它们始终会在其他代码之前运行。

  注意:脚本的下载顺序不固定,但执行时会按照你将文件名传入到  importScripts() 中的顺序。这是同步完成的;直到所有脚本都下载并运行完毕,  importScripts() 才会返回。

  atob() 、btoa()  base64编码与解码的方法。

  也可以使用XMLHttpRequest对象来做Ajax通信,以及其他API :WebSocket、Promise、Worker

  终止 web worker

  如果你想立即终止一个运行中的 worker,可以调用 worker 的  terminate()方法。被终止的Worker对象不能被重启或重用,我们只能新建另一个Worker实例来执行新的任务。
  1. myWorker.terminate();
复制代码

  处理错误

  当 worker 出现运行时错误时,它的  onerror 事件处理函数会被调用。它会收到一个 实现了  ErrorEvent  接口 名为   error 的事件, 供开发者捕捉错误信息。下面的代码展示了如何绑定error事件:
  1. worker.addEventListener("error", function(evt){  
  2. alert("Line #" + evt.lineno + " - " + evt.message + " in " + evt.filename);  
  3. }, false);  
复制代码

  如上可见, Worker对象可以绑定error事件;而且evt对象中包含错误所在的代码文件(evt.filename)、错误所在的代码行数(evt.lineno)、以及错误信息(evt.message)。

  下面上一个完整的dedicated web worker 使用案例。

  demo_worker.html
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>Title</title>
  6. </head>
  7. <body>
  8. <p>Count numbers:
  9.     <output id="result"></output>
  10. </p>
  11. <button id="startWorker">startWorker</button>
  12. <button id="endWorker">stopWorker</button>
  13. </body>
  14. <script>
  15.     (function () {
  16.         var result = document.querySelector('#result'),
  17.                 startWorker = document.querySelector('#startWorker'),
  18.                 endWorker = document.querySelector('#endWorker'),
  19.                 worker,
  20.                 data = 10;
  21.         startWorker.addEventListener('click', function (event) {
  22.             //console.log('click event', event);
  23.             if (typeof Worker !== 'undefined') {
  24.                 if (typeof worker == "undefined") {
  25.                     worker = new Worker('./demo_workers.js');
  26.                 }
  27.                 worker.addEventListener('message', function (event) {
  28.                     //console.log('message event', event);
  29.                     result.innerHTML = event.data;
  30.                 }, false);
  31.                 worker.addEventListener("error", function (event) {
  32.                     alert("Line #" + event.lineno + " - " + event.message + " in " + event.filename);
  33.                 }, false);
  34.                 worker.postMessage(data);
  35.                 endWorker.addEventListener('click', function () {
  36.                     worker.terminate();
  37.                 }, false);
  38.             } else {
  39.                 result.innerHTML = 'sry, your browser does not support Web workers...';
  40.             }
  41.         }, false);
  42.     })();
  43. </script>
  44. </html>
复制代码

  这个HTML页面中有个startWorker按钮,点击后会运行一个Javascript文件。上面的代码中首先检测当前浏览器是否支持Web Worker,不支持的话就显示提醒信息。

  按钮的点击事件中创建了Worker对象,并给它指定了Javascript脚本文件—— demo_workers .js(稍后会有代码),并且给Worker对象绑定了一个“message”事件。该事件会在后台代码( demo_workers .js)向页面返回数据时触发。“message”事件可以通过event.data来获取后台代码传回的数据。最后,postMessage方法正式执行 demo_workers .js,该方法向后台代码传递参数,后台代码同样通过message事件参数的data属性获取。

  demo_worker.js
  1. addEventListener('message',function (event) {
  2.     var count = event.data;
  3.     var interval = setInterval(function () {
  4.         postMessage(count--);!count && clearInterval(interval);
  5.     },1000);

  6. });
复制代码

  以上代码在后台监听message事件,并获取页面传来的参数;这里实际上是一个从10到1的倒计时:在message事件被触发之后,把结果传给页面显示出来。

  所以当点击startWorker按钮,页面会在count number: 显示从10递减一变为最终的1,在这10秒钟内页面依然可以响应鼠标键盘事件。点击stopWorker按钮,web worker 会直接终止,页面变化显示会直接停止。

  嵌入式web worker
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>MDN Example - Embedded worker</title>
  6. <script type="text/js-worker">
  7.   // 该脚本不会被 JS 引擎解析,因为它的 mime-type 是 text/js-worker。
  8.   var myVar = "Hello World!";
  9.   // 剩下的 worker 代码写到这里。
  10. </script>
  11. <script type="text/javascript">
  12.   // 该脚本会被 JS 引擎解析,因为它的 mime-type 是 text/javascript。
  13.   function pageLog (sMsg) {
  14.     // 使用 fragment:这样浏览器只会进行一次渲染/重排。
  15.     var oFragm = document.createDocumentFragment();
  16.     oFragm.appendChild(document.createTextNode(sMsg));
  17.     oFragm.appendChild(document.createElement("br"));
  18.     document.querySelector("#logDisplay").appendChild(oFragm);
  19.   }
  20. </script>
  21. <script type="text/js-worker">
  22.   // 该脚本不会被 JS 引擎解析,因为它的 mime-type 是 text/js-worker。
  23.   onmessage = function (oEvent) {
  24.     postMessage(myVar);
  25.   };
  26.   // 剩下的 worker 代码写到这里。
  27. </script>
  28. <script type="text/javascript">
  29.   // 该脚本会被 JS 引擎解析,因为它的 mime-type 是 text/javascript。

  30.   // 在过去...:
  31.   // 我们使用 blob builder
  32.   // ...但是现在我们使用 Blob...:
  33.   var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});

  34.   // 创建一个新的 document.worker 属性,包含所有 "text/js-worker" 脚本。
  35.   document.worker = new Worker(window.URL.createObjectURL(blob));

  36.   document.worker.onmessage = function (oEvent) {
  37.     pageLog("Received: " + oEvent.data);
  38.   };

  39.   // 启动 worker.
  40.   window.onload = function() { document.worker.postMessage(""); };
  41. </script>
  42. </head>
  43. <body><div id="logDisplay"></div></body>
  44. </html>
复制代码

  现在,嵌入式 worker 已经嵌套进了一个自定义的  document.worker 属性中。

  在 worker 内创建 worker

  worker 的一个优势在于能够执行处理器密集型的运算而不会阻塞 UI 线程。在下面的例子中,worker 用于计算斐波那契数。

  fibonacci.js
  1. var results = [];

  2. function resultReceiver(event) {
  3.   results.push(parseInt(event.data));
  4.   if (results.length == 2) {
  5.     postMessage(results[0] + results[1]);
  6.   }
  7. }

  8. function errorReceiver(event) {
  9.   throw event.data;
  10. }

  11. onmessage = function(event) {
  12.   var n = parseInt(event.data);

  13.   if (n == 0 || n == 1) {
  14.     postMessage(n);
  15.     return;
  16.   }

  17.   for (var i = 1; i <= 2; i++) {
  18.     var worker = new Worker("fibonacci.js");
  19.     worker.onmessage = resultReceiver;
  20.     worker.onerror = errorReceiver;
  21.     worker.postMessage(n - i);
  22.   }
  23. };
复制代码

  worker 将属性  onmessage 设置为一个函数,当 worker 对象调用  postMessage() 时该函数会接收到发送过来的信息。 (注意,这么使用并不等同于定义一个同名的全局 变量 ,或是定义一个同名的 函数 。 var onmessage 与  function onmessage 将会定义与该名字相同的全局属性,但是它们不会注册能够接收从创建 worker 的网页发送过来的消息的函数。) 这会启用递归,生成自己的新拷贝来处理计算的每一个循环。

  fibonacci.html
  1. <!DOCTYPE html>
  2. <html>
  3.   <head>
  4.     <meta charset="UTF-8"  />
  5.     <title>Test threads fibonacci</title>
  6.   </head>
  7.   <body>

  8.   <div id="result"></div>

  9.   <script>

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

  11.     worker.onmessage = function(event) {
  12.       document.getElementById("result").textContent = event.data;
  13.       dump("Got: " + event.data + "\n");
  14.     };

  15.     worker.onerror = function(error) {
  16.       dump("Worker error: " + error.message + "\n");
  17.       throw error;
  18.     };

  19.     worker.postMessage("5");

  20.   </script>
复制代码

  网页创建了一个  div 元素,ID 为  result , 用它来显示运算结果,然后生成 worker。在生成 worker 后, onmessage 处理函数配置为通过设置  div 元素的内容来显示运算结果,最后,向 worker 发送一条信息来启动它。

相关帖子

发表于 2017-1-5 10:28:03 来自手机 | 显示全部楼层
兼容性方面,HTML5确实有优势
使用道具 举报

回复

发表于 2017-1-7 00:07:56 | 显示全部楼层
占坑编辑ing
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部