FrankKai.github.io
FrankKai.github.io copied to clipboard
一次失败的用web worker提升速度的实践
- web worker解决什么问题?
- vue-cli3 项目配置( webpack4 worker-loader2 )
- 手上项目测试demo( webpack3 worker-loader1 )
- 手上项目web worker 实践(迁移热力图计算逻辑)
- 总结与思考
web worker解决什么问题?
由于JavaScript是单线程的,当一段js代码执行耗时过久时,会有阻塞的情况出现。 web worker就是解决阻塞的一种方式。它开启了一个一个worker thread,将复杂计算放在worker线程中运行,将同步代码转换为异步代码。
可以看这个例子。
main thread同步方式:
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
let myDate;
for(let i = 0; i < 10000000; i++) {
let date = new Date();
myDate = date
}
console.log(myDate);
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
main thread和worker thread异步方式:
const btn = document.querySelector('button');
const worker = new Worker('worker.js');
btn.addEventListener('click', () => {
worker.postMessage('Go!');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
worker.onmessage = function(e) {
console.log(e.data);
}
worker.js
onmessage = function() {
let myDate;
for(let i = 0; i < 10000000; i++) {
let date = new Date();
myDate = date
}
postMessage(myDate);
}
vue-cli3 项目配置( webpack4 worker-loader2 )
// 安装work-loader2.0.0
yarn add -D worker-loader
// vue.config.js
chainWebpack: config => {
// 配置worker-loader
config.module
.rule("web worker")
.test(/\.worker\.js$/)
.use("worker-loader")
.loader("worker-loader")
.tap(() => {
// 这一行配置非常重要
return { inline: true };
})
.end();
}
手上项目测试demo( webpack3 worker-loader1 )
安装和配置
// 安装work-loader1.1.1
yarn add -D [email protected]
这是因为worker-loader2.0.0移除了对webpack3的支持,而work-loader1.1.1既支持w3也支持w4。可查看worker-loader release日志。
// webpack.base.conf.js
module: {
rules: [{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
inline: true,
},
},
}]
}
demo测试
worker线程
// src/workers/test.worker.js
onmessage = function(evt) {
// 工作线程收到主线程的消息
console.log("worker thread :", evt); // {data:{msg:”Hello worker thread.“}}
// 工作线程向主线程发送消息
postMessage({
msg: "Hello main thread."
});
};
main线程
// src/pages/worker.vue
<template>
<div>Main thread</div>
</template>
<script>
import TestWorker from "../workers/test.worker.js";
export default {
name: "worker",
created() {
const worker = new TestWorker();
// 主线程向工作线程发送消息
worker.postMessage({ msg: "Hello worker thread." });
// 主线程接收到工作线程的消息
worker.onmessage = function(event) {
console.log("main thread", event); // {data:{msg:"Hello main thread."}}
};
}
};
</script>
手上项目web worker 实践(迁移热力图计算逻辑)
安装和配置与上面一致。
工作线程
// src/workers/heat.worker.js
function cellClassNameSet() { ... }
function extractMinMax() { ... }
onmessage = (event) => {
const { type, metaData } = event.data;
// 生成热力样式
if (type === 'HEAT_GENERATE') {
const sweetData = cellClassNameSet(metaData);
postMessage({ status: 'HEAT_GENERATE_SUCCESS', sweetData });
}
// 生成最大最小值
if (type === 'MAX_MIN_EVALUATE') {
const sweetData = extractMinMax(metaData);
postMessage({ status: 'MAX_MIN_EVALUATE_SUCCESS', sweetData });
}
};
主线程
// src/pages/heat.vue
<scripts>
import HeatWorker from '../workers/heat.worker';
// 引入web worker,尝试提升计算性能
let worker = new HeatWorker();
created(){
this.heatWebWorkerStart()
},
beforeDestory() {
worker.terminate();worker = null;
},
methods: {
heatWebWorkerStart() {
worker.onmessage = (event) => {
const { status, sweetData } = event.data;
if (status === 'HEAT_GENERATE_SUCCESS') {
// 设置primitiveIndex用于显示隐藏条目
// ...do something with sweetData
this.getSliderMinMax();
}
if (status === 'MAX_MIN_EVALUATE_SUCCESS') {
// ...do something with sweetData
// 等热力计算和最大最小值计算完成,刷新监听器
}
};
},
// 构建出热力图
generateBeautyCell() {
worker.postMessage({ type: 'HEAT_GENERATE', metaData });
},
// 计算范围最大最小值
getSliderMinMax() {
worker.postMessage({ type: 'MAX_MIN_EVALUATE', metaData });
},
}
</scripts>
但是添加工作线程之后,自身体感功能有如下变化:
- 首次渲染明显看到DOM与样式间隔渲染。
- 引入工作线程后,明显感到主线程UI渲染更加卡顿。
经过性能分析工具分析之后,有如下分析:
- 间隔因为工作线程中的复杂计算逻辑不阻塞主线程DOM渲染,渲染完成后再执行
- 明显卡顿因为开启工作线程以及工作主线程之间进行数据传递较为昂贵,导致性能降低
总结与思考
失败就真的没有收获吗?
做了一次web worker在项目上的尝试,虽然失败但也是经验的积累。
学到了如何配置webpack的worker-loader;主线程与工作线程之间如何进行数据传递;主线程在控制台的sources面板会开启一个独立的工作线程。
web worker应用场景探索
web worker的应用场景据说包括光线追踪(Ray Tracing),加密,数据预获取,Progressive Web App,拼写检查。
其他选项与业务场景脱离,但是数据预处理方面在项目中是可以实践的。
引用一段How JavaScript works: The building blocks of Web Workers + 5 cases when you should use them 中的话
数据预获取: 为优化你的网站或 web 应用的数据加载时长,你可以使用 Web Worker 预先获取一些数据,存储起来以备后续使用。Web Worker 在这里发挥着重要作用,因为它绝不会影响应用的 UI 体验,若不使用 Web Worker 情况会变得异常糟糕。
数据预处理方面我想可以结合IndexedDB,localStorage等前端存储(可以用到localForage去择优选择前端存储)去做一些数据的预加载,比如图片,音频,视频等静态资源的预加载,通过工作线程默默地用户无感知地将资源缓存到本地,提升用户体验。其实主要是预先存储一些大块的内容,暂时不用但是后续会用到的。
有机会可以做一些实践尝试。