前言
面试的时候有被问到一个场景:一个页面渲染程的需要计算上亿的次的计算,如何可以将页面尽量快的完成渲染
- 一开始以为是考
<script>
写的位置(body的底部),或者defer属性(先下载渲染后再加载),但是似乎都不是面试官想要的答案 - 直到学习到了Web Worker…
- 一开始以为是考
Web Worker
概述:由浏览器提供的web Api,主要是为了个js提供多线程的能力,另开一线程执行代码,兼容性
执行过程:使用代码
new Worker('url')
去加载url的代码(网络),然后在另外的线程执行url的代码,主线程和worker线程通过Worker.postMessage发送数据和window.onmessage接收数据来进行交互限制:同源政策,内容安全策略(指定)
使用:
- main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// main.js
if (window.Worker) {
const ww = new Worker('./worker.js');
let first = document.querySelector('#first')
ww.postMessage('go')
// first.onchange = function (v) {
// console.log(v.target.value)
// ww.postMessage(v.target.value)
// }
ww.onmessage = function (e) {
result = e.data;
console.log('Message received from worker');
first.value = result
ww.terminate();
}
ww.onerror = function(e){
console.log('get error %o',e)
}
} else {
console.log('not support web worker')
}- worker.js
1
2
3
4
5
6
7
8
9
10
11
12// worker.js
onmessage = function (e) {
console.log('Message received from main script');
const start = new Date().getTime()
let sum = 0
for(let i=0;i<10000000000;i++){
sum += i
}
console.log(new Date().getTime() - start)
console.log('Posting message back to main script');
postMessage(sum);
}- 使用后关闭主线程
worker.terminate()
,或这worker内部close()
JavaScript是单线程的同时在浏览器中和GUI渲染的线程互斥,前面开头的提到的场景中:
如果停下渲染html去执行计算,那么进入页面就会空白一段时间等js加载完再出现页面ui
如果使用把script直接放到html的body标签的最后,页面ui会渲染出来先,但在渲染完成后到js计算完成之前,页面ui交互可能会报错(计算未完成);因为计算时间长,所以是可能在这段时间用户进行ui交互(可以加个“加载中”动画缓解用户焦虑,类似请求完成前一直loading)
time = renderHtml + downloadJs + runJs
如果使用
<script>
的defer,可以先并行下载后执行,相对上一点只是节省了一下下载时间(几乎忽略不计)time = max(renderHtml, downloadJs) + runJs
如果使用web worker,先直接加载使用worker的js代码,然后渲染html;因为是另开线程来执行,所以不阻塞html渲染,页面ui应该会马上出来;因为页面和js计算基本同时执行,所以如果计算较大,使用Web Worker是最快的;
time = max(renderHtml, downloadJs + runJs)
当然无论如何,计算量大总是会有问题,所以加个加载动画是最好的,让用户看到页面又屏蔽用户的操作。
参考:
最后更新: 2021年07月16日 00:07