加入收藏 | 设为首页 |

lol押注网站-JavaScript多线程计划:Web Workers看完即会的教程

海外新闻 时间: 浏览:277 次

Web Worker为Web内容在后台线程中运转脚本供给了一种简略的办法。线程能够履行任务而不搅扰用户界面。此外,他们能够运用XMLHttpRequest履行 I/O (虽然responseXML和channel特点总是为空)。一旦创立, 一个worker 能够将音讯发送到创立它的JavaScript代码, 经过将音讯发布到该代码指定的工作处理程序(反之亦然)。本文供给了有关运用Web Worker的具体介绍。

Web Workers API

一个worker是运用一个结构函数创立的一个目标(e.g. Worker()) 运转一个命名的JavaScript文件 - 这个文件包含将在作业线程中运转的代码; workers 运转在另一个大局上下文中,不同于当时的window. 因而,运用 window快捷办法获取当时大局的规模 (而不是self) 在一个 Worker内将回来过错。

在专用workers的状况下,DedicatedWorkerGlobalScope 目标代表了worker的上下文(专用workers是指规范worker仅在单一脚本中被运用;同享worker的上下文是SharedWorkerGlobalScope目标)。一个专用worker仅仅能被初次生成它的脚本运用,而同享worker能够一起被多个脚本运用。

在worker线程中你能够运转任何你喜爱的代码,不过有一些例外状况。比方:在worker内,不能直接操作DOM节点,也不能运用window目标的默许办法和特点。可是你能够运用很多window目标之下的东西,包含WebSockets,IndexedDB以及FireFox OS专用的Data Store API等数据存储机制。查看Functions and classes available to workers获取概况。

workers和主线程间的数据传递经过这样的音讯机制进行——两边都运用postMessage()办法发送各自的音讯,运用onmessage工作处理函数来呼应音讯(音讯被包含在Message工作的data特点中)。这个进程中数据并不是被同享而是被仿制。

只需运转在同源的父页面中,workers能够顺次生成新的workers;而且能够运用XMLHttpRequest 进行网络I/O,responseXML和XMLHttpRequest的通道特点一向回来null的状况在外。

专用worker

如前文所述,一个专用worker仅仅能被生成它的脚本所运用。这一部分将评论 专用worker根底示例 (运转专用worker) 中的JavaScript代码:将你输入的2个数字作乘法。输入的数字会发送给一个专用worker,由专用worker作乘法后,再回来给页面进行展现。

这个比方很小,可是咱们决定在坚持简略的一起向你介绍根底的worker概念。更多的细节会在之后的文章中进行解说。

worker特性检测

为了更好的过错处理操控以及向下兼容,将你的worker运转代码包裹在以下代码中是一个很好的主意(main.js):

if (window.Worker) {
...
}

生成一个专用worker

创立一个新的worker很简略。你需求做的是调用Worker() 的结构器,指定一个脚本的URI来履行worker线程(main.js):

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

专用worker中音讯的接纳和发送

workers的魔法经过postMessage() 办法和onmessage工作处理函数收效。向一个worker发送音讯需求这样做(main.js):

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

这段代码中变量first和second代表2个元素;它们傍边恣意一个的值发作改动时,myWorker.postMessage([first.value,second.value])会将这2个值组成数组发送给worker。你能够在音讯中发送许多你想发送的东西。

在worker中接纳到音讯后,咱们能够写这样一个工作处理函数代码作为呼应(worker.js):

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);
}

onmessage处理函数答应咱们在任何时刻,一旦接纳到音讯就能够履行一些代码,代码中音讯自身作为工作的data特点进行运用。这儿咱们简略的对这2个数字作乘法处理并再次运用postMessage()办法,将成果回传给主线程。

回到主线程,咱们再次运用onmessage以呼应worker回传的音讯:

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

在这儿咱们获取音讯工作的data,而且将它设置为result的textContent,所以用户能够直接看到运算的成果。

留意: 在主线程中运用时,onmessage和postMessage() 有必要挂在worker目标上,而在worker中运用时不必这样做。原因是,在worker内部,worker是有用的大局效果域。当一个音讯在主线程和worker之间传递时,它被仿制或许转移了,而不是同享。

停止worker

假如你需求从主线程中马上停止一个运转中的worker,能够调用worker的terminate 办法:

myWorker.terminate();

worker 线程会被当即杀死,不会有任何时机让它完结自己的操作或整理作业。

而在worker线程中,workers 也能够调用自己的 close 办法进行封闭:

close();

处理过错

当 worker 呈现运转中过错时,它的 onerror 工作处理函数会被调用。它会收到一个扩展了 ErrorEvent 接口的名为 error的工作。

该工作不会冒泡而且能够被撤销;为了避免触发默许动作,worker 能够调用过错工作的 preventDefault()办法。

过错工作有以下三个用户关怀的字段:

message

可读性杰出的过错音讯。

filename

发作过错的脚本文件名。

lineno

发作过错时地点脚本文件的行号。

生成subworker

假如需求的话 worker 能够生成更多的 worker。这便是所谓的subworker,它们有必要保管在同源的父页面内。而且,subworker 解析 URI 时会相关于父 worker 的地址而不是自身页面的地址。这使得 worker 更简略记载它们之间的依靠联系。

引进脚本与库

Worker 线程能够拜访一个大局函数importScripts()来引进脚本,该函数承受0个或许多个URI作为参数来引进资源;以下比方都是合法的:

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

阅读器加载并运转每一个列出的脚本。每个脚本中的大局目标都能够被 worker 运用。假如脚本无法加载,将抛出 NETWORK_ERROR 反常,接下来的代码也无法履行。而之前履行的代码(包含运用 window.setTimeout() 异步履行的代码)依然能够运转。importScripts() 之后的函数声明依然会被保存,由于它们一直会在其他代码之前运转。

留意: 脚本的下载次序不固定,但履行时会依照传入 importScripts() 中的文件名次序进行。这个进程是同步完结的;直到一切脚本都下载并运转结束,importScripts() 才会回来。

同享worker

一个同享worker能够被多个脚本运用——即便这些脚本正在被不同的window、iframe或许worker拜访。这一部分,咱们会评论同享worker根底示例(运转同享worker)中的javascript代码:该示例与专用worker根底示例非常相像,仅仅有2个可用函数被存放在不同脚本文件中:两数相乘函数,以及求平方函数。这两个脚本用同一个worker来完结实际需求的运算。

这儿,咱们重视一下专用worker和同享worker之间的差异。在这个示例中有2个HTML页面,每个页面所包含的javascript代码运用的是同一个worker。

留意:假如同享worker能够被多个阅读上下文调用,一切这些阅读上下文有必要归于同源(相同的协议,主机和端口号)。在 Firefox中, 同享worker不能被私有和非私有window目标的document所同享 (bug 1177621)。

生成一个同享worker

生成一个新的同享worker与生成一个专用worker非常相似,仅仅结构器的姓名不同(查看 index.html 和 index2.html)——生成同享worklol押注网站-JavaScript多线程计划:Web Workers看完即会的教程er的代码如下:

var myWorker = new SharedWorker('worker.js');

一个非常大的差异在于,与一个同享worker通讯有必要经过端口目标——一个切当的翻开的端口供脚本与worker通讯(在专用worker中这一部分是隐式进行的)。

在传递音讯之前,端口衔接有必要被显式的翻开,翻开办法是运用onmessage工作处理函数或许start()办法。示例中的 multiply.js 和 worker.js 文件没有调用了start()办法,这些调用并不那么重要是由于onmessage工作处理函数正在被运用。start()办法的调用只在一种状况下需求,那便是音讯工作被addEventListener()办法运用。

在运用start()办法翻开端口衔接时,假如父级线程和worker线程需求双向通讯,那么它们都需求调用start()办法。

myWorker.port.start(); // 父级线程中的调用
port.start(); // worker线程中的调用, 假定port变量代表一个端口

同享worker中音讯的接纳和发送

现在,音讯能够像之前那样发送到worker了,可是postMessage() 办法有必要被端口目标调用(你会再一次看到 multiply.js 和 square.js中相似的结构):lol押注网站-JavaScript多线程计划:Web Workers看完即会的教程

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

回到worker中,这儿也有一些些杂乱(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);
}
}

首要,当一个端口衔接被创立时(例如:在父级线程中,设置onmessage工作处理函数,或许显式调用start()办法时),运用onconnect工作处理函数来履行代码。

运用工作的ports特点来获取端口并存储在变量中。

然后,为端口增加一个音讯处理函数用来做运算并回传成果给主线程。在worker线程中设置此音讯处理函数也会隐式的翻开与主线程的端口衔接,因而这儿跟前文相同,对port.start()的调用也是不必要的。

终究,回到主脚本,咱们处理音讯(你会又一次看到 multiply.js 和 square.js中相似的结构):

myWorker.port.onmessage = function(e) {
result2.textContent = e.datalol押注网站-JavaScript多线程计划:Web Workers看完即会的教程;
console.log('Message received from worker');
}

当一条音讯经过端口回到worker,咱们查看成果的类型,然后将运算成果放入成果阶段中适宜的当地。

关于线程安全

Worker接口会生成真实的操作体系等级的线程,假如你不太当心,那么并发会对你的代码发生风趣的影响。可是,关于 web worker 来说,与其他线程的通讯点会被很当心的操控,这意味着你很难引起并发问题。你没有办法去拜访非线程安全的组件或许是 DOM,此外你还需求经过序列化目标来与线程交互特定的数据。所以你要是不费点劲儿,还真搞不出过错来

内容安全策略

有别于创立它的document目标,worker有它自己的履行上下文。因而遍及来说,worker并不受限于创立它的document(或许父级worker)的内容安全策略。咱们来举个比方,假定一个document有如下头部声明:

Content-Security-Policy: script-src 'self'

这个声明有一部分效果在于,制止它内部包含的脚本代码运用eval()办法。可是,假如脚本代码创立了一个worker,在worker上下文中履行的代码却是能够运用eval()的。

为了给worker指定内容安全策略,有必要为发送worker代码的恳求自身加上一个 内容安全策略。

有一个例外状况,即worker脚本的源假如是一个大局性的仅有的标识符(例如,它的URL指定了数据形式或许blob),worker则会承继创立它的document或许worker的CSP(Content security policy内容安全策略)。

worker中数据的接纳与发送:具体介绍节

在主页面与 worker 之间传递的数据是经过复制,而不是同享来完结的。传递给 worker 的目标需求经过序列化,接下来在另一端还需求反序列化。页面与 worker 不会同享同一个实例,终究的成果便是在每次通讯结束时生成了数据的一个副本。大部分阅读器运用结构化复制来完结该特性。

在往下进行之前,出于教育的意图,让咱们创立一个名为 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);
};

结构化复制算法能够接纳JSON数据以及一些JSON不能表明的数据——比方循环引证

传递数据的比方

比方 #1: 创立一个通用的异步 eval()

下面这个比方介绍了,如安在 worker 内运用 eval() 来按次序履行异步的任何品种的 JavaScript 代码:

// Syntax: asyncEval(code[, listener])
var asyncEval = (function () {
var aListeners = [];
var oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%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
});
};
})();

data URL 相当于一个网络恳求,它有如下回来:

onmessage = function(oEvent) {
postMessage({
'id': oEvent.data.id,
'evaluated': eval(oEvent.data.code)
});
}

示例运用:

// 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 XMLHttpReque乾陵st();\n\toReq.open(\"get\", \"http://www.mozilla.org/\", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()");

比方 #2:传输 JSON 的高档办法和创立一个交流体系

假如你需求传输非常杂乱的数据,还要一起在主页与 Worker 内调用多个办法,那么能够考虑创立一个相似下面的体系。

首要,咱们创立一个QueryableWorker的类,它接纳worker的url、一个默许侦听函数、和一个过错处理函数作为参数,这个类将会记载一切的侦听的列表而且协助咱们与worker进行通讯。

function QueryableWorker(url, defaultListener, onError) {
var instance = this,
worker = new Worker(url),
listeners = {};
this.defaultListener = defaultListener || function() {};

if (onError) {worker.onerror = onError;}

this.postMessage = function(message) {
worker.postMessage(message);
}
this.terminate = function() {
worker.terminate();
}
}

紧接着,咱们写出新增和删去侦听的办法。

this.addListeners = function(name, listener) {
listeners[name] = listener;
}
this.removeListeners = function(name) {
delete listeners[name];
}

这儿咱们让worker处理2个这样的简略操作:差异2个数字并在3秒后弹框提示。为了完结这个操作,咱们首要完结一个sendQuery办法,该办法能够查询worker是否真实有咱们所需求的对应办法。

/* 
This functions takes at least one argument, the method name we want to query.
Then we can pass in the arguments that the method needs.
*/
this.sendQuery = function() {
if (arguments.length < 1) {
throw new TypeError('QueryableWorker.sendQuery takes at least one argument');
return;
}
worker.postMessage({
'queryMethod': arguments[0],
'queryArguments': Array.prototype.slice.call(arguments, 1)
});
}

咱们以onmessage办法作为QueryableWorker的结束。假如worker有咱们所需求的对应的办法,它就会回来相对应的侦听办法的姓名以及所需求的参数,咱们只需求在侦听列表listeners中找到它:

worker.onmessage = function(event) {
if (event.data instanceof Object &&
event.data.hasOwnProperty('queryMethodListener') &&
event.data.hasOwnProperty('queryMethodArguments')) {
listeners[event.data.queryMethodListener].apply(instance, event.data.queryMethodArguments);
} else {
this.defaultListener.call(instance, event.data);
}
}

现在回到worker中。首要咱们需求一个能够完结这2个操作的办法:

var queryableFunctions = {
getDifference: function(a, b) {
reply('printStuff', a - b);
},
waitSomeTime: function() {
setTimeout(function() {
reply('doAlert', 3, 'seconds');
}, 3000);
}
}
function reply() {
if (arguments.length < 1) {
throw new TypeError('reply - takes at least one argument');
return;
}
postMessage({
queryMethodListener: arguments[0],
queryMethodArguments: Array.prototype.slice.call(arguments, 1)
});
}
/* This method is called when main page calls QueryWorker's postMessage method directly*/
function defaultReply(message) {
// do something
}

onmessage办法也就很简略了:

onmessage = function(event) {
if (event.data instanceof Object &&
event.data.hasOwnProperty('queryMethod') &&
event.data.hasOwnProperty('queryMethodArguments')) {
queryableFunctions[event.data.queryMethod]
.apply(self, event.data.queryMethodArguments);
} else {
defaultReply(event.data);
}
}

接下来给出一个完好的完结:

example.html (the main page):

my_task.js (the worker):

这个实例中,能够对从主页面到worker、以及worker到主页面之间传递的音讯内容进行切换。而且特点名"queryMethod", "queryMethodListeners","queryMethodArguments"可所以任何东西,只需它们在QueryableWorker和worker中坚持一致。

经过转让一切权(可转让目标)来传递数据

Google Chrome 17 与 Firefox 18 包含另一种功能更高的办法来将特定类型的目标(可转让目标) 传递给一个 worker/从 worker 传回 。可转让目标从一个上下文转移到另一个上下文而不会经过任何复制操作。这意味着当传递大数据时会取得极大的功能提高。假如你从 C/C++ 国际来,那么把它幻想成依照引证传递。可是与依照引证传递不同的是,一旦目标转让,那么它在本来上下文的那个版别将不复存在。该目标的一切权被转让到新的上下文内。例如,当你将一个 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]);

嵌入式 worker

现在没有一种「官方」的办法能够像


网页创立了一个 div 元素,ID 为 result , 用它来显现运算成果,然后生成 worker。在生成 worker 后,onmessage 处理函数装备为经过设置 div 元素的内容来显现运算成果,然后 onerror 处理函数被设置为 转储 过错信息。

终究,向 worker 发送一条信息来发动它。

worker中可用的函数和接口

你能够在web worker中运用大多数的规范javascript特性,包含

  • Navigator
  • XMLHttpRequest
  • Array, Date, Math, and String
  • WindowTimers.setTimeout and WindowTimers.setInterval

在一个worker中最首要的你不能做的工作便是直接影响父页面。包含操作父页面的节点以及运用页面中的目标。你只能间接地完结,经过DedicatedWorkerGlobalScope.postMessage回传音讯给主脚本,然后从主脚本那里履行操作或改变。

Worker兼容性

参阅文章

https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers

https://caniuse.com/#search=Web%20Worker