# 解决方案
# 1. 利用webWorker解决页面阻塞
在CI CD模块里,有渲染代码扫码日志功能,后端返回数据吞吐量超过20000+行,这时候会造成主线程上的活动(比如用户点击按钮、提交表单)打断,出现页面卡顿或者页面“假死”的情况。这样有利于随时响应主线程的通信。
启用Worker,计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
// main.js
const worker = new Worker('./work.js')
// 输出参数
worker.postMessage(44)
// 接收响应
worker.onmessage = ({ data }) => {
console.log('result', data) // result=>: 701408733
}
// worker.js
// 这里使用上面的fibonacci函数的例子做回调
// 当前作用域 只能使用globalThis 或者 self 指向DedicatedWorkerGlobalScope实例
// 接收响应:
globalThis.onmessage = ({ data }) => {
console.log({ data }) // 40
console.time('time==>')
let result = fib(data)
console.timeEnd('time==>') // result=>: 1722.83984375
// 回调内部指向的是当前实例
this.postMessage(result)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- Web Worker 使用教程
- 《JavaScript高级程序设计(第4版)》—— 工作者线程 第27章 / 791页
# 2. 长列表匹配
data.filter(({id}) => {
return selectedIds.includes(id)
})
2
3
就是这样几行代码,逻辑就是筛选出data里面已经被勾选的数据。基本上很多人都可能这么写,因为我看我们团队里面都是这么写的。产品当时已经限制data最多200数据,所以写完完全没压力,性能没影响。但是秉着对性能优化的原则(主要是被生产环境搞怕了~~~),我开启了mock服务,将数据调到了2万条再去测试,代码弊端就暴露出来了,界面进入卡顿,重新选择的时候也会卡顿。然后就开始了优化,当时具体的思路如下:
按照现在的代码来看,这是一个两层循环的暴力搜索时间复杂度为O(n^2)。所以想着能不能降一下复杂度至少是O(nlogn),看了一下代码只能从selectedIds.includes(id)这句入手,于是想着可不可以用二分,但是立马被否定因为二分是需要有序的,我这数组都是字符串怎么二分。
暴力搜索的方法基本都是:
- 上指针
- 数组升维
- 利用hash表
前两者被我否定了因为我觉得还没那么复杂,于是利用hash表思想解决这个问题,因为js里面有一个天然的hash表结构就是对象。我们知道hash表的查询是O(1)的,所以我将代码改写如下
// 更改后代码
const ids = {}
selectedIds.forEach(id => ids[id] = 1)
data.filter(({id}) => !!ids[id])
2
3
4
5
将从selectedIds查询变成从ids查询,这样时间复杂度就从O(n^2)变成了O(n)了。
其实增加了一个selectedIds遍历也是一个O(n)的复杂度,总来说复杂度是O(2n),但是从时间复杂度长期期望来看还是一个O(n)的时间复杂度,只不过额外增加了一个对象,所以这也是一个典型的空间换时间的例子,但是也不要担心,ids用完之后垃圾回收机制会把他回收的。
# 3. 不要轻易使用setInterval
# 3.1 setInterval的弊端:
不会关心回调函数是否还在运行 在某些情况下,函数可能需要比间隔时间更长的时间去完成执行。比如说是用
setInterval每隔5秒对远端服务器进行轮询,网络延迟,服务器无响应以及其他因素将会阻止请求按时按成。结果会导致返回一串没必要的排成队列请求。忽视错误 因为某些原因,
setInterval调用的代码中会出现一个错误,但是代码并不会中止执行而是继续执行错误的代码。缺乏灵活性 除了前面提到的缺点之外,期望
setInterval方法能有一个表明执行次数的参数而不是无休止的执行下去。
# 3.2 利用setTimeout实现setInterval
class SetInterval(){
this.tasks = []
add(name, callback, time =0){
if(!name || !callback || typeof callback !== 'function') return
this.tasks = push({
name,
callback,
time
})
this.run(name)
}
run(name){
const _this = this
function inner(){
const task = _this.tasks.find(item => item.name === name)
if(!task) return
const timer = setTimeout(()=>{
task.callback()
clearTimeout(timer)
inner()
}, task.time)
}
inner()
}
remove(name){
const taskIndex = this.tasks.findIndex(task => task.name === name)
if(taskIndex !== -1){
this.tasks.splice(taskIndex, 1)
}
}
clearAll(){
this.tasks = []
}
}
// 使用方法
const s = new SetInterval()
s.add('test', ()=>{ console.log('custom setInterval')}, 1000)
s.run('test')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 3.3 使用setTimeout实现倒计时,消除时间误差(待填)
- 深度解密setTimeout和setInterval——为setInterval正名!
- 《JavaScript高级程序设计(第4版)》—— 12.1.7定时器 / 368页