在web开发中,前端是单线程操作的,当前端有大运算任务时,前端会出现卡顿的情况,影响页面性能。为了将任务在后台操作进行,可以使用
setTimeout
,但是其实也只是在前端空闲的时间间隔中运行而已。
web worker采用webkit机制,可以创建很多子线程,我们需要运转的计算任务就可以交给子线程来完成,不影响页面性能,主线程则可以继续监听用户前端的请求。
下面详细介绍一下 web worker
setTimeout的使用
setTimeout 表示在指定时间(毫秒)之后执行函数
setTimeout(function() {
console.log("this is settimeout function");
}, 1000);
web worker的特点
允许用户在后台长时间运行而不被中断,就是允许javascript创建多个线程,但是子线程完全受主线程控制,且不得操作DOM,从而可以用web worker来处理一些比较耗时的计算。
主线程中通过new Worker
创建的worker对象,并用onmessage
方法接受worker.js
里面postMessage
传递过来的数据event.data
,并将数据追加到 result 的DOM中,例子如下:
/*
* main.html
*/
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html"; charset="utf-8">
<title>
Background Worker Application Example 1: Complicated Number Computation
</title>
</head>
<body>
<div>
The least common multiple and greatest common divisor is:
<p id="computation_results">please wait, computing ... </p>
</div>
<script>
var paras = {first: 347734080, second: 3423744400},
worker = new Worker('worker.js');
worker.postMessage(JSON.stringify(paras));
worker.onmessage = function (event) {
document.getElementById('computation_results').textContent = event.data;
worker.terminate();
};
worker.onerror = function(error) {
console.error('[worker error]: ', error.message, '; [error file]: ', error.filename, '; [error line]: ', error.lineno);
console.error('[worker error detail]: ', error)
worker.terminate();
};
</script>
</body>
</html>
/*
* worker.js
*/
/*
* location的值如下
* {
* href: "http://localhost:8888/worker.js",
* origin: "http://localhost:8888",
* protocol: "http:",
* host: "localhost:8888",
* hostname: "localhost",
* port: "8888",
* pathname: "/worker.js",
* hash: "",
* search: ""
* }
*/
console.table(self.location);
importScripts(self.location.origin + '/math_utilities.js'); // woker.js 可以加载js文件
self.onmessage = function (event) {
var event_data = JSON.parse(event.data),
first = event_data.first,
second = event_data.second;
calculate(first, second);
};
function calculate(first, second) {
var common_divisor = divisor(first, second),
common_multiple = multiple(first, second);
self.postMessage("Work done! " +
"The least common multiple is " + common_divisor
+ " and the greatest common divisor is " + common_multiple);
}
/*
* math_utilities.js
*/
function divisor(a, b) {
if (a % b == 0) {
return b;
} else {
return divisor(b, a % b);
}
}
function multiple(a, b) {
var multiple = 0;
multiple = a * b / divisor(a, b);
return multiple;
}
web worker常用API
-
postMessage(data) -> 子线程与主线程之间互相通信使用的方法,传递的data为任意值
// main thread var newWorker = new Worker("url"); newWorker.postMessage({first: 1, second: 2}); // 传递给子线程数据 // dedicated worker // 专用线程,不仅支持传输二进制数据,也支持结构化的javascript数据格式 // 主线程与自线程的数据通讯是拷贝关系,即是传值,而不是地址,所以子线程对通讯内容的修改,不会影响到主线程(但会影响性能); // 在主线程传递二进制数据到子线程数据量比较大时,可以不用上面的拷贝关系;而是直接把数据转移给子线程,此时主线程无法在使用这些数据 // 方法是在 postMessage 方法第二个参数中指定它 newWorker.postMessage({ operation: 'list_all_users', input: buffer, // ArrayBuffer object threshold: 0.8 }, [buffer]); // child thread postMessage('test'); // 子线程传递给主线程数据
-
terminate() -> 主线程中终止worker,此后无法再利用其进行消息传递。 注意:一旦terminate之后,无法重新启用,只能另外创建
// main thread newWorker.terminate();
onmessage -> 当有消息发送时,触发该事件;并且消息发送是双向,消息内容可通过data来获取
-
onerror -> 出错处理;并且错误消息可以通过e.message 来获取
// main thread newWorker.onerror = function (e) { console.error(e.message); newWorker.terminate(); };
注意事项:
- worker 线程从上到下同步运行它的代码,然后进入异步阶段来对事件进行响应。
- 如果worker注册了message事件处理程序,只要其有可能触发,worker就一直在内存中不会退出;
- 所以通信完毕后得手动在主线程中terminate或者子线程中close掉(但是一般在主线程中使用terminate方法关闭通信,原因是防止子线程任务失败,这样通信链接还存在,就不会触发close了);
worker环境变量说明
worker.js 执行的上下文,与主页面html执行时的并不相同,最顶层的对象并不是 window,worker.js执行时的上下文叫做WorkerGlobalScope,所以无法访问window、与window相关的DOM API
WorkerGlobalScope的环境变量如下:
- self -> 表示这个worker对象本身的引用
- location -> 表示用于初始化这个工作线程的脚本资源的绝对URL
- close -> 关闭当前线程,与主线程中的terminate作用类似
- importScript -> 通过此函数可以在worker中通过url加载库函数,顺序加载
- XMLHttpRequest -> 发出Ajax请求
- setTimeout/setInterval 以及 addEventListener/postMessage
worker的局限性
- 不能跨域加载JS
- worker内代码不能访问DOM
- 各个浏览器对Worker的实现不太一致,例如firefox里允许worker中创建新的Worker,而chrome中就不行
- 在主线程中通过postMessage方法传递过去的对象,在通过onMessage传递过来了,这也只是它的一个副本,而不是同一个内存地址(防止多个线程同事操作对象)