HTML5之web workers

日期:2020-10-13 类型:科技新闻 

关键词:在线网页制作,建网页,个人简介网页制作,简单网页,建立网页

专用 Web Worker (Dedicated Web Worker) 出示了1个简易的方式使得 web 內容可以在后台管理运作脚本制作。1旦 worker 建立后,它能够向由它的建立者特定的恶性事件监视涵数传送信息,这样该 worker 转化成的全部每日任务就都会接受到这些信息。worker 进程可以在不影响 UI 的状况下实行每日任务。此外,它还可以应用XMLHttpRequest(尽管responseXML与channel 两个特性值自始至终是null)来实行I/O 实际操作。本文根据出示事例和细节补全了前面的文本文档。出示给 worker 的涵数列出了 worker 所适用的涵数。

Worker插口会转化成真实的实际操作系统软件级別的进程,假如你不很小心,那末高并发(concurrency)会对你的编码造成趣味的危害。但是,针对 web worker 来讲,与别的进程的通讯点会被很当心的操纵,这代表着你很难引发高并发难题。你沒有方法去浏览非进程安全性的组件或是 DOM,另外你还必须根据编码序列化目标来与进程互动特殊的数据信息。因此你如果不费点劲儿,还真搞不错误误来。转化成 worker

建立1个新的 worker 10分简易。你所要做的便是启用Worker()结构涵数,特定1个要在 worker 进程内运作的脚本制作的 URI,假如你期待可以收到 worker 的通告,能够将 worker 的onmessage特性设定成1个特殊的恶性事件解决涵数。

var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
  console.log("Called back by the worker!\n");
};

或,你还可以应用addEventListener():

var myWorker = new Worker("my_task.js");
myWorker.addEventListener("message", function (oEvent) {
  console.log("Called back by the worker!\n");
}, false);
myWorker.postMessage(""); // start the worker.

事例中的第1行建立了1个新的 worker 进程。第3个人行为 worker 设定了message恶性事件的监视涵数。当 worker 启用自身的postMessage() 涵数时就会启用这个恶性事件解决涵数。最终,第7行起动了 worker 进程。留意: 传入Worker结构涵数的主要参数 URI 务必遵照同宗对策。现阶段,不一样的访问器生产制造商针对哪些 URI 应当遵照同宗对策尚有矛盾;Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0 / SeaMonkey 2.7) 及后续版本号容许传入 data URI,而 Internet Explorer 10 则不觉得 Blob URI 针对 worker 来讲是1个合理的脚本制作。

传送数据信息

在首页面与 worker 之间传送的数据信息是根据复制,而并不是共享资源来进行的。传送给worker 的目标必须历经编码序列化,接下来在另外一端还必须反编码序列化。网页页面与 worker不容易共享资源同1个案例,最后的結果便是在每次通讯完毕时转化成了数据信息的1个副本。绝大多数访问器应用构造化复制来完成该特点。

在往下开展以前,出于课堂教学的目地,让大家建立1个名为emulateMessage()的涵数,它将仿真模拟在从worker到首页面(反之亦然)的通讯全过程中,自变量的「复制而非共享资源」个人行为:

function emulateMessage (vVal) {
    return eval("(" + JSON.stringify(vVal) + ")");
}
// Tests
// test #1
var example1 = new Number(3);
alert(typeof example1); // object
alert(typeof emulateMessage(example1)); // number
// test #2
var example2 = true;
alert(typeof example2); // boolean
alert(typeof emulateMessage(example2)); // boolean
// test #3
var example3 = new String("Hello World");
alert(typeof example3); // object
alert(typeof emulateMessage(example3)); // string
// test #4
var example4 = {
    "name": "John Smith",
    "age": 43
};
alert(typeof example4); // object
alert(typeof emulateMessage(example4)); // object
// test #5
function Animal (sType, nAge) {
    this.type = sType;
    this.age = nAge;
}
var example5 = new Animal("Cat", 3);
alert(example5.constructor); // Animal
alert(emulateMessage(example5).constructor); // Object

复制而并不是共享资源的那个值称为信息。再来谈谈worker,你可使用postMessage() 将信息传送给主进程或从主进程传输回家。message恶性事件的data特性就包括了从 worker 传回家的数据信息。

example.html: (首页面):

var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
  console.log("Worker said : " + oEvent.data);
};
myWorker.postMessage("ali");
my_task.js (worker):
postMessage("I\'m working before postMessage(\'ali\').");
onmessage = function (oEvent) {
  postMessage("Hi " + oEvent.data);
};

留意:一般来讲,后台管理进程 – 包含 worker –没法实际操作 DOM。假如后台管理进程必须改动 DOM,那末它应当将信息推送给它的建立者,让建立者来进行这些实际操作。

如你所见,worker与首页面之间传送的信息自始至终是「JSON 信息」,即便它是1个初始种类的值。因此,你彻底能够传送JSON数据信息 和/或 任何可以编码序列化的数据信息种类:

postMessage({"cmd": "init", "timestamp": Date.now()});

传送数据信息的事例

事例 #1: 建立1个通用性的 「多线程eval()」

下面这个事例详细介绍了,怎样在 worker 内应用eval()来按序实行多线程的任何类型的 JavaScript 编码:

// Syntax: asyncEval(code[, listener])
var asyncEval = (function () {
  var aListeners = [], oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%三d%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D");
  oParser.onmessage = function (oEvent) {
    if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); }
    delete aListeners[oEvent.data.id];
  };
  return function (sCode, fListener) {
    aListeners.push(fListener || null);
    oParser.postMessage({
      "id": aListeners.length - 1,
      "code": sCode
    });
  };
})();

示例应用:

// asynchronous alert message...
asyncEval("3 + 2", function (sMessage) {
    alert("3 + 2 = " + sMessage);
});
// asynchronous print message...
asyncEval("\"Hello World!!!\"", function (sHTML) {
    document.body.appendChild(document.createTextNode(sHTML));
});
// asynchronous void...
asyncEval("(function () {\n\tvar oReq = new XMLHttpRequest();\n\toReq.open(\"get\", \"http://www.mozilla.org/\", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()");

事例 #2:传送 JSON 的高級方法和建立1个互换系统软件

假如你必须传送十分繁杂的数据信息,还要另外在首页与 Worker 内启用好几个方式,那末能够考虑到建立1个相近下面的系统软件。

example.html(the main page):

<!doctype html>
<html>
<head>
<meta charset="UTF⑻"  />
<title>MDN Example - Queryable worker</title>
<script type="text/javascript">
  /*
    QueryableWorker instances methods:
     * sendQuery(queryable function name, argument to pass 1, argument to pass 2, etc. etc): calls a Worker's queryable function
     * postMessage(string or JSON Data): see Worker.prototype.postMessage()
     * terminate(): terminates the Worker
     * addListener(name, function): adds a listener
     * removeListener(name): removes a listener
    QueryableWorker instances properties:
     * defaultListener: the default listener executed only when the Worker calls the postMessage() function directly
  */
  function QueryableWorker (sURL, fDefListener, fOnError) {
    var oInstance = this, oWorker = new Worker(sURL), oListeners = {};
    this.defaultListener = fDefListener || function () {};
    oWorker.onmessage = function (oEvent) {
      if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("vo42t30") && oEvent.data.hasOwnProperty("rnb93qh")) {
        oListeners[oEvent.data.vo42t30].apply(oInstance, oEvent.data.rnb93qh);
      } else {
        this.defaultListener.call(oInstance, oEvent.data);
      }
    };
    if (fOnError) { oWorker.onerror = fOnError; }
    this.sendQuery = function (/* queryable function name, argument to pass 1, argument to pass 2, etc. etc */) {
      if (arguments.length < 1) { throw new TypeError("QueryableWorker.sendQuery - not enough arguments"); return; }
      oWorker.postMessage({ "bk4e1h0": arguments[0], "ktp3fm1": Array.prototype.slice.call(arguments, 1) });
    };
    this.postMessage = function (vMsg) {
      //I just think there is no need to use call() method
      //how about just oWorker.postMessage(vMsg);
      //the same situation with terminate
      //well,just a little faster,no search up the prototye chain
      Worker.prototype.postMessage.call(oWorker, vMsg);
    };
    this.terminate = function () {
      Worker.prototype.terminate.call(oWorker);
    };
    this.addListener = function (sName, fListener) {
      oListeners[sName] = fListener;
    };
    this.removeListener = function (sName) {
      delete oListeners[sName];
    };
  };
  // your custom "queryable" worker
  var oMyTask = new QueryableWorker("my_task.js" /* , yourDefaultMessageListenerHere [optional], yourErrorListenerHere [optional] */);
  // your custom "listeners"
  oMyTask.addListener("printSomething", function (nResult) {
    document.getElementById("firstLink").parentNode.appendChild(document.createTextNode(" The difference is " + nResult + "!"));
  });
  oMyTask.addListener("alertSomething", function (nDeltaT, sUnit) {
    alert("Worker waited for " + nDeltaT + " " + sUnit + " :-)");
  });
</script>
</head>
<body>
  <ul>
    <li><a id="firstLink" href="javascript:oMyTask.sendQuery('getDifference', 5, 3);">What is the difference between 5 and 3?</a></li>
    <li><a href="javascript:oMyTask.sendQuery('waitSomething');">Wait 3 seconds</a></li>
    <li><a href="javascript:oMyTask.terminate();">terminate() the Worker</a></li>
  </ul>
</body>
</html>
my_task.js (the worker):
// your custom PRIVATE functions
function myPrivateFunc1 () {
  // do something
}
function myPrivateFunc2 () {
  // do something
}
// etc. etc.
// your custom PUBLIC functions (i.e. queryable from the main page)
var queryableFunctions = {
  // example #1: get the difference between two numbers:
  getDifference: function (nMinuend, nSubtrahend) {
      reply("printSomething", nMinuend - nSubtrahend);
  },
  // example #2: wait three seconds
  waitSomething: function () {
      setTimeout(function() { reply("alertSomething", 3, "seconds"); }, 3000);
  }
};
// system functions
function defaultQuery (vMsg) {
  // your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
  // do something
}
function reply (/* listener name, argument to pass 1, argument to pass 2, etc. etc */) {
  if (arguments.length < 1) { throw new TypeError("reply - not enough arguments"); return; }
  postMessage({ "vo42t30": arguments[0], "rnb93qh": Array.prototype.slice.call(arguments, 1) });
}
onmessage = function (oEvent) {
  if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("bk4e1h0") && oEvent.data.hasOwnProperty("ktp3fm1")) {
    queryableFunctions[oEvent.data.bk4e1h0].apply(self, oEvent.data.ktp3fm1);
  } else {
    defaultQuery(oEvent.data);
  }
};

这是1个十分适合的方式,用于切换 首页-worker - 或是相反的 - 之间的信息。

根据出让全部权(可出让目标)来传送数据信息

Google Chrome 17 与 Firefox 18 包括另外一种特性更高的方式来将特殊种类的目标(可出让目标) 传送给1个 worker/从 worker 传回 。可出让目标从1个左右文迁移到另外一个左右文而不容易历经任何复制实际操作。这代表着当传送绝大多数据时会得到巨大的特性提高。假如你从 C/C++ 全球来,那末把它想像成依照引入传送。但是与依照引入传送不一样的是,1旦目标出让,那末它在原先左右文的那个版本号将不复存在。该目标的全部权被出让到新的左右文内。比如,当你将1个ArrayBuffer目标从主运用出让到Worker 中,初始的ArrayBuffer被消除而且没法应用。它包括的內容会(详细无差的)传送给 Worker 左右文。

// Create a 32MB "file" and fill it.
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
  uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

转化成subworker

假如必须的话 Worker 可以转化成更多的 Worker。这样的被称为 subworker,它们务必代管在与父网页页面同样的源内。同理,subworker 分析 URI 时会相对父 worker 的详细地址而并不是本身的网页页面。这使得 worker 非常容易监管它们的依靠关联。 Chrome 现阶段其实不适用subworker。

嵌入式 worker

现阶段沒有1种「官方」的方式可以像<script>元素1样将 worker 的编码嵌入的网页页面中。可是假如1个<script>元素沒有src 特点,而且它的type特点沒有特定成1个可运作的 mime-type,那末它就会被觉得是1个数据信息块元素,而且可以被 JavaScript 应用。「数据信息块」是 HTML5 中1个10分普遍的特点,它能够携带基本上任何文字种类的数据信息。因此,你可以以以下方法嵌入1个 worker:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF⑻" />
<title>MDN Example - Embedded worker</title>
<script type="text/js-worker">
  // 该脚本制作不容易被 JS 模块分析,由于它的 mime-type 是 text/js-worker。
  var myVar = "Hello World!";
  // 剩余的 worker 编码写到这里。
</script>
<script type="text/javascript">
  // 该脚本制作会被 JS 模块分析,由于它的 mime-type 是 text/javascript。
  function pageLog (sMsg) {
    // 应用 fragment:这样访问器只会开展1次3D渲染/重排。
    var oFragm = document.createDocumentFragment();
    oFragm.appendChild(document.createTextNode(sMsg));
    oFragm.appendChild(document.createElement("br"));
    document.querySelector("#logDisplay").appendChild(oFragm);
  }
</script>
<script type="text/js-worker">
  // 该脚本制作不容易被 JS 模块分析,由于它的 mime-type 是 text/js-worker。
  onmessage = function (oEvent) {
    postMessage(myVar);
  };
  // 剩余的 worker 编码写到这里。
</script>
<script type="text/javascript">
  // 该脚本制作会被 JS 模块分析,由于它的 mime-type 是 text/javascript。
  // 在以往...:
  // 大家应用 blob builder
  // ...可是如今大家应用 Blob...:
  var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"});
  // 建立1个新的 document.worker 特性,包括全部 "text/js-worker" 脚本制作。
  document.worker = new Worker(window.URL.createObjectURL(blob));
  document.worker.onmessage = function (oEvent) {
    pageLog("Received: " + oEvent.data);
  };
  // 起动 worker.
  window.onload = function() { document.worker.postMessage(""); };
</script>
</head>
<body><div id="logDisplay"></div></body>
</html>

如今,嵌入式 worker 早已嵌套循环进了1个自定的document.worker特性中。

请求超时与间距

Worker 可以像主进程1样应用请求超时与间距。这会10分有效,例如说,假如你想让 worker 进程周期性而并不是不中断的运作编码。

停止 worker

假如你想马上停止1个运作中的 worker,能够启用 worker 的terminate()方式:

myWorker.terminate();

worker 进程会被马上杀死,不容易留下任何机遇让它进行自身的实际操作或清除工作中。

Workers 还可以启用自身的nsIWorkerScope.close()方式来关掉自身:

self.close();

解决不正确

当 worker 出現运作时不正确时,它的onerror恶性事件解决涵数会被启用。它会收到1个完成了ErrorEvent插口名为error的恶性事件。该恶性事件不容易冒泡,而且能够被撤销;以便避免开启默认设置姿势,worker 能够启用不正确恶性事件的preventDefault()方式。不正确恶性事件有着以下3个它感兴趣爱好的字段:

message

可读性优良的不正确信息。

filename

产生不正确的脚本制作文档名。

lineno

产生不正确时所属脚本制作文档的行号。

浏览 navigator 目标

Workers 能够在它的功效域内浏览navigator目标。它含有以下可以鉴别访问器的标识符串,就像在一般脚本制作中做的那样:

  • appName
  • appVersion
  • platform
  • userAgent

引进脚本制作与库

Worker 进程可以浏览1个全局性涵数,importScripts(),该涵数容许 worker 将脚本制作或库引进自身的功效域内。你能够不传入主要参数,或传入好几个脚本制作的 URI 来引进;下列的事例全是合理合法的:

importScripts();                        /* 甚么都不引进 */
importScripts('foo.js');                /* 只引进 "foo.js" */
importScripts('foo.js', 'bar.js');      /* 引进两个脚本制作 */

访问器将列出的脚本制作载入并运作。每一个脚本制作中的全局性目标都可以被 worker 应用。假如脚本制作没法载入,将抛出NETWORK_ERROR出现异常,接下来的编码也没法实行。而以前实行的编码(包含应用window.setTimeout()延迟时间实行的编码)却仍然可以应用。importScripts()以后的涵数申明仍然可以应用,由于它们自始至终会在别的编码以前运作。留意:脚本制作的免费下载次序不固定不动,但实行时会依照你将文档名传入到importScripts()中的次序。这是同歩进行的;直至全部脚本制作都免费下载并运作结束,importScripts()才会回到。

事例

本节出示了几个怎样应用 DOM worker 的事例。

在后台管理实行运算

worker 的1个优点在于可以实行解决器聚集型的运算而不容易堵塞 UI 进程。在下面的事例中,worker 用于测算斐波那契数。

JavaScript 编码

下面的 JavaScript 编码储存在「fibonacci.js」文档中,与下1节的 HTML 文档关系。

var results = [];
function resultReceiver(event) {
  results.push(parseInt(event.data));
  if (results.length == 2) {
    postMessage(results[0] + results[1]);
  }
}
function errorReceiver(event) {
  throw event.data;
}
onmessage = function(event) {
  var n = parseInt(event.data);
  if (n == 0 || n == 1) {
    postMessage(n);
    return;
  }
  for (var i = 1; i <= 2; i++) {
    var worker = new Worker("fibonacci.js");
    worker.onmessage = resultReceiver;
    worker.onerror = errorReceiver;
    worker.postMessage(n - i);
  }
 };

worker 将特性onmessage设定为1个涵数,当 worker 目标启用postMessage() 时该涵数会接受到推送过来的信息内容。(留意,这么应用其实不等同于于界定1个同名的全局性自变量,或是界定1个同名的涵数。var onmessage与function onmessage可能界定与该姓名同样的全局性特性,可是它们不容易申请注册可以接受从建立 worker 的网页页面推送过来的信息的涵数。) 这会开启递归,转化成自身的新复制来解决测算的每个循环系统。

HTML 编码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF⑻"  />
    <title>Test threads fibonacci</title>
  </head>
  <body>
  <div id="result"></div>
  <script language="javascript">
    var worker = new Worker("fibonacci.js");
    worker.onmessage = function(event) {
      document.getElementById("result").textContent = event.data;
      dump("Got: " + event.data + "\n");
    };
    worker.onerror = function(error) {
      dump("Worker error: " + error.message + "\n");
      throw error;
    };
    worker.postMessage("5");
  </script>
  </body>
</html>

网页页面建立了1个div元素,ID 为result, 用它来显示信息运算結果,随后转化成 worker。在转化成 worker 后,onmessage解决涵数配备为根据设定div元素的內容来显示信息运算結果,随后onerror解决涵数被设定为转储不正确信息内容。最终,向 worker 推送1条信息内容来起动它。