Js是单线程的,如何实现多线程?
浅谈Web Worker
Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,允许我们在 js 主线程之外开辟新的 Worker 线程,并将一段 js 脚本运行其中,它赋予了开发者利用 js 操作多线程的能力。
因为是独立的线程,Worker 线程与 js 主线程能够同时运行,互不阻塞。所以,在我们有大量运算任务时,可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 js 主线程。这样,js 主线程只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而减少了阻塞时间,也提高了运行效率,页面流畅度和用户体验自然而然也提高了。
相较异步,异步仍然遵循单线程规则,多个异步方法还是会按顺序执行。
代码介绍
1.主线程采用new命令,调用Worker()构造函数,新建一个 Worker 线程。
const worker = new Worker('./work.js');
2.主线程调用worker.postMessage()方法,向 Worker 发消息。
它可以是各种数据类型,包括二进制数据。
worker.postMessage('Hello World');
3.主线程通过worker.onmessage指定监听函数,接收子线程发回来的消息。
worker.onmessage = function (event) {
console.log('Received message ' + event.data);
doSomething();
}
function doSomething() {
// 执行任务
worker.postMessage('Work done!');
}
// 这里的worker.onmessage 也可以换成self.addEventListener ,self代表子线程自身,即子线程的全局对象 等同于
self.addEventListener('message', function (e) {
self.postMessage('You said: ' + e.data);
}, false);
4.Worker 完成任务以后,主线程就可以把它关掉。
worker.terminate();
5.Worker 内部如果要加载其他脚本,有一个专门的方法importScripts()
。
importScripts('script1.js', 'script2.js');
6.主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的error事件。
worker.onerror(function (event) {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
// 或者
worker.addEventListener('error', function (event) {
// ...
});
web worker的简单应用
主线程
const worker = new Worker('./worker.js');
worker.addEventListener('message', function ({data}) {
switch (data.type) {
case 'prime':
document.getElementById('prime').textContent = `${data.n}之内的所有质数是:${data.result.join(",")}`;
break;
case 'fibonacci':
document.getElementById('fibonacci').textContent = `${data.n}之内的所有斐波那契数列之和是:${data.result}`;
break;
case 'reverseNumber':
document.getElementById('reverseNumber').textContent = `${data.n}之内的所有回文数是:${data.result}`;
break;
default:
break;
}
}, false);
worker.postMessage({type: 'prime', n: 300000});
worker.postMessage({type: 'fibonacci', n: 40});
worker.postMessage({type: 'reverseNumber', n: 400000});
worker.js
self.addEventListener('message', function ({ data }) {
switch (data.type) {
case 'prime': // 质数
self.postMessage({ type: 'prime', n: data.n, result: countPrime(data.n) });
break;
case 'fibonacci': //斐波那契数列
self.postMessage({ type: 'fibonacci', n: data.n, result: fibonacci(data.n) });
break;
case 'reverseNumber': // 回文数
self.postMessage({ type: 'reverseNumber', n: data.n, result: countReverseNumber(data.n) });
default:
break;
}
}, false);
// 计算n以内的所有质数
function countPrime(num) {
let n = 1;
let nums = [];
search:
while (n < num) {
// 开始搜寻下一个质数
n += 1;
for (let i = 2; i <= Math.sqrt(n); i++) {
// 如果除以n的余数为0,开始判断下一个数字。
if (n % i == 0) {
nums.push(n)
continue search;
}
}
}
return nums;
}
// 计算斐波那契数列之和
function fibonacci(n) {
if (n == 1 || n == 2) return 1;
return n >= 3 ? fibonacci(n - 1) + fibonacci(n - 2) : null;
}
// 计算
function countReverseNumber(n) {
return Array.from(new Array(n), (v,i) => (i+1)).filter(v => {
let nv = v.toString().split('').reverse().join('')
return nv == v && v > 10
})
}
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被用于占用大量CPU资源的程序中,比如2D canvas 和矢量图,webGL数据索引计算等。因为worker位于另一个线程中,它不会阻断主线程中的任何任务,比如UI渲染。如果能将worker运用自如,它的效果将十分强大。包括IE10在内的众多浏览器都能够很好地支持这一功能。