概述
Javascript是单线程模型,所有任务在一个线程上完成且一次只能处理一个任务,单线程是为了防止DOM混乱。
webworker的作用就是为JS创建多个线程,允许在主线程之外创建Woker线程(叫做子线程),在主线程运行的同时,Worker线程也在后台(一个新的js环境WorkerGlobalScope)运行,两者互不干扰,Worker完成计算任务之后,再将计算结果返回给主线程。一些计算复杂、延迟高的任务由Worker线程来负担,主线程(通常指UI线程)就可以比较流畅不被阻塞了。
目前除了IE基本所有浏览器都支持Web Worker。
与EventLoop的区别
EventLoop
浏览器线程包括 UI线程(渲染页面)、JS引擎线程(处理js)、GUI事件线程(用户交互)、http传输线程(xhr请求)、定时触发线程(定时器)等。上述浏览器的多个线程并不是真正的多线程,实际上都是有JS线程来处理程序。
- UI线程 与 JS引擎线程 互斥,JS阻塞页面加载
- JS引擎线程 与 GUI线程 异步
- JS引擎线程 与 http传输线程 异步
- JS引擎线程 与 定时器线程 异步
同步任务是立即执行的任务,直接进入到主线程中执行;异步任务(xhr请求、setTimeout等)会通过EventQueue进行协调。主线程内的任务执行完成为空后,会去读区Event Queue中的任务并推入煮线程执行,这个不断重复的过程也就是EventLoop。
WebWorker
WebWorker 是真正意义上的多线程,只是创建子线程收到主线程的控制。
webworker的应用场景
大计算量的代码
- js单线程会发生阻塞。
- 计时器事件不准。 将这部分计算代码放到worker线程,不会导致主线程被刮起。
webworker的使用
基本用法
主线程
- 创建 worker 线程
/**
* new Worker(jsURL[, options])
* @param jsUrl 表示worker将要执行的脚本URL
* @param options (可选)
* @options type: 'classic', // 可选值为 classic 或 module ,默认classic
* @options credentials: 'omit',
* @options name 设置 worker的名称 DedicatedWorkerGlobalScope 全局对象的name属性。可以通过 _self.name 访问
*/
const myWorker = new Worker('worker.js')
}
- 主线程向worker线程发送消息
myWorker.postMessage('hello myWorker');
myWorker.postMessage({ method: 'print', message: 'hello myWorker' });
- 主线程接受worker线程发来的消息
myWorker.onmessage = function(e) {
console.log('Message received from worker is ', e.data);
}
- 错误处理
myWorker.onerror(function (event) {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
- 关闭worker线程
myWorker.terminate();
worker线程
- worker线程接受消息
onmessage = function(e) {
console.log('Worker: Message received from main script', e.data.message);
}
- worker线程向主线程发送消息
onmessage = function(e) {
console.log('Message received from main script', e.data.message);
console.log('Posting message back to main script');
postMessage('Message from worker');
}
- worker加载脚本
importScripts('subWorker.js');
- 关闭worker线程
self.close();
同页面的webworker
上述情况为引入外部js文件,也可以载入与主线程再同一个文件的代码。
- toString() 将JS函数转化成字符串
- new Blob(stringFun) 将worker脚本代码专程二进制对象。
- 将二进制对象生成URL。
- new Worker(URL) 创建worker线程。
// index.js
const workerBlock = function(e) {
console.log('Message received from main script', e.data.message);
console.log('Posting message back to main script');
postMessage('Message from worker');
}
const blob = new Blob([`onmessage=${workerBlock.toString()}`], { "type": "text/javascript" });
const url = window.URL.createObjectURL(blob);
const myWorker = new Worker(url);
myWorker.postMessage('hello myWorker');
myWorker.postMessage({ method: 'print', message: 'hello myWorker' });
myWorker.onmessage = function(e) {
console.log('Message received from worker is ', e.data);
}
worker API
主线程
- onerror 指定 error 事件监听函数。
- onmessage 指定 message 事件的监听函数,发送过来的数据可以通过
$event.data
访问。 - onmessageerror 指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时会触发这个事件。
- postMessage 向 worker 线程发送消息。
- terminate 立即终止 worker 线程。
worker 线程
- name (属性, 只读) worker 的名字,有new Worker指定。
- onmessage 指定 message 事件的监听函数。
- onmessageerror 指定 messageerror 事件的监听函数。
- close 关闭 worker 线程。
- postMessage 向创建该 worker 线程的主程序发送消息。
- importScripts 加载JS脚本。
worker 的全局对象 WorkerGlobalScope
WorkerGlobalScope 是 worker 的全局对象,包含JS全局对象的属性,例如JSON等,还有window的一些属性,以及XMLHttpRequest等。
webworker的注意事项
- 脚本必须来自网络且与主线程脚本同源,无法加载本地文件系统(
file://
) - 不能访问DOM;不能访问document、window、parent等对象;可以访问navigator、location(只读)、XMLHttpRequest、setTimeout等浏览器API。
- 不能执行alert()、confirm()方法。
- 主线程和worker线程不在同一个上下文环境,不能直接通信必须通过消息完成。
webpack下使用webworker
- 安装 worker-loader
npm i -D worker-loader
- 解析worker文件
module.exports = {
chainWebpack: config => {
// GraphQL Loader
config.module
.rule('worker-loader')
.test(/\.worker\.js$/)
.use('worker-loader')
.loader('worker-loader')
.end()
}
}
- 使用 worker.js
import Worker from './file.worker.js';
const worker = new Worker()