js为单线程,但web workers可以使得一些涉及复杂计算的逻辑在独立的线程运行,从而不会影响页面的性能,例如渲染、交互响应等(本质为主线程专注ui渲染)
不能操作DOM、作用域独立等
DediactedWorker: 线程只能与一个页面渲染进程进行绑定和通信,不能多 Tab 共享
SharedWorker: 可以多个浏览器 Tab 中访问到同一个 Worker 实例,实现多 Tab 共享数据,共享 websocket 等(safari 放弃了 SharedWorker 支持)
主线程: 管理内容渲染和用户交互,包含步骤:JavaScript -> Style -> Layout -> Paint -> Composit
主线程和worker通过postMessage
和onmessage
来通信。
// index.html
const worker = new Worker('./worker.js');
// 向worker发送消息。
worker.postMessage('start');
// 接受worker发送的消息
worker.onmessage = function onmessage(ev) {
document.getElementById('result').innerHTML = ev.data;
if (ev.data === 10) worker.terminate();
};
// worker.js
onmessage = function (ev) {
if (ev.data === 'start') {
timedCount();
}
};
const timedCount = function timedCount() {
i += 1;
// 向主线程发送消息
postMessage(i);
setTimeout(timedCount, 500);
};
在vue3中的使用
yarn add worker-loader
在vue.config.js文件的defineConfig里加上配置参数
chainWebpack: config => {
config.module
.rule('worker-loader')
.test(/\.worker\.js$/)
.use({
loader: 'worker-loader',
options: {
inline: true
}
})
.loader('worker-loader')
.end()
}
// vue页面
import Worker from 'worker-loader!./js/worker'; // 注意后面为文件路径
const makeWorker = () => {
// 获取计算开始的时间
const start = performance.now();
// // 新建一个线程
const worker = new Worker();
// 线程之间通过postMessage进行通信
worker.postMessage(0);
// 监听message事件
worker.addEventListener('message', (e: any) => {
// 关闭线程
worker.terminate();
// 获取计算结束的时间
const end = performance.now();
// 得到总的计算时间
const durationTime = end - start;
console.log('分线程计算结果:', e.data);
console.log(`分线程代码执行了 ${durationTime} 毫秒`);
});
};
// worker.js
onmessage = function (e) {
// onmessage获取传入的初始值
let sum = e.data;
for (let i = 0; i < 200000; i++) {
for (let i = 0; i < 10000; i++) {
sum += Math.random();
}
}
// 将计算的结果传递出去
postMessage(sum);
};
在vue2中的使用
安装
yarn add vue-worker
引入
import VueWorker from 'vue-worker'
Vue.use(VueWorker)
主页面
import worker from "./transformDataToTree";
this.$worker.run(worker, [参数],then(res1, res2)=>{
// 一些逻辑执行之后的回调操作
})
worker.js
export default (接收到的参数) => {
// 一些执行逻辑
const treeData1 = []
const treeData2 = []
return {
res1: treeData1, // 返回的数值可以在主页面回调中接收
res2: treeData2
}
}
备注:
1、vue项目中,若直接使用Web Worker,会遇到worker文件路径与打包解析的问题
2、vue2中适合使用vue-worker, 若在vue3中使用的话,web-worker库会报Object.defineProperty called on non-object的错误
Shared worket提供了一种跨窗口/iframe等通信的途径(同源通信)
注: chrome浏览器通过chrome://inspect/#workers
进入可查看线程
线程标识源自解析后的脚本 URL、工作者线程名称和文档源(第二个参数为SharedWorke名称),任何一个不同都会创建新的进程
以命名为例
const worker = new SharedWorker("./js/shareWorker.js", 'page1');
const worker = new SharedWorker("./js/shareWorker.js", 'page2');
shareWorker.js
// 记个数
let count = 0;
// 把每个连接的端口存下来
const ports = [];
// 连接函数 每次创建都会调用这个函数
onconnect = (e) => {
console.log("这里是共享线程展示位置");
// 获取端口
const port = e.ports[0];
// 把丫存起来
ports.push(port);
// 监听方法
port.onmessage = (msg) => {
// 这边的console.log是看不到的 debugger也是看不到的 需要在线程里面看
console.log("共享线程接收到信息:", msg.data, count);
if (msg.data === "+") {
count++;
}
// 循环向所有端口广播
ports.forEach((p) => {
p.postMessage(count);
});
};
};
page1.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SharedWorker-page1</title>
</head>
<body>
<h1>SharedWorker-page1</h1>
<button onclick="workerClick()">count++</button>
<script>
// 兼容性判断
if (!SharedWorker) {
throw new Error("当前浏览器不支持SharedWorker");
}
// 创建
const worker = new SharedWorker("./js/shareWorker.js", 'page1');
// 启动
worker.port.start();
// 线程监听消息
worker.port.onmessage = (e) => {
console.log("page1共享线程计数值:", e.data);
};
const workerClick = () => {
worker.port.postMessage('+');
};
</script>
</body>
</html>
page2.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SharedWorker-page2</title>
</head>
<body>
<h1>SharedWorker-page2</h1>
<button onclick="workerClick()">count++</button>
<script>
const btn = document.querySelector('#btn');
// 兼容性判断
if (!SharedWorker) {
throw new Error('当前浏览器不支持SharedWorker');
}
// 创建
const worker = new SharedWorker('./js/shareWorker.js', 'page2');
// 启动
worker.port.start();
// 线程监听消息
worker.port.onmessage = (e) => {
console.log('page2共享线程计数值:', e.data);
};
const workerClick = () => {
worker.port.postMessage('+');
};
</script>
</body>
</html>
数据处理逻辑放入worker中处理即可
Canvas计算和渲染和用户操作响应都发生在同一个线程,会耗时产生页面卡顿问题.
OffscreenCanvas 离屏Canvas将渲染与DOM完全分离,正因为不依赖dom,所以可以在计算过程可以在worker中进行,避免阻塞住进程.