# 解决方案

# 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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 2. 长列表匹配

data.filter(({id}) => {
  return selectedIds.includes(id)
})
1
2
3

就是这样几行代码,逻辑就是筛选出data里面已经被勾选的数据。基本上很多人都可能这么写,因为我看我们团队里面都是这么写的。产品当时已经限制data最多200数据,所以写完完全没压力,性能没影响。但是秉着对性能优化的原则(主要是被生产环境搞怕了~~~),我开启了mock服务,将数据调到了2万条再去测试,代码弊端就暴露出来了,界面进入卡顿,重新选择的时候也会卡顿。然后就开始了优化,当时具体的思路如下:

按照现在的代码来看,这是一个两层循环的暴力搜索时间复杂度为O(n^2)。所以想着能不能降一下复杂度至少是O(nlogn),看了一下代码只能从selectedIds.includes(id)这句入手,于是想着可不可以用二分,但是立马被否定因为二分是需要有序的,我这数组都是字符串怎么二分。

暴力搜索的方法基本都是:

  1. 上指针
  2. 数组升维
  3. 利用hash表

前两者被我否定了因为我觉得还没那么复杂,于是利用hash表思想解决这个问题,因为js里面有一个天然的hash表结构就是对象。我们知道hash表的查询是O(1)的,所以我将代码改写如下

// 更改后代码
const ids = {}
selectedIds.forEach(id => ids[id] = 1)

data.filter(({id}) => !!ids[id])
1
2
3
4
5

将从selectedIds查询变成从ids查询,这样时间复杂度就从O(n^2)变成了O(n)了。

其实增加了一个selectedIds遍历也是一个O(n)的复杂度,总来说复杂度是O(2n),但是从时间复杂度长期期望来看还是一个O(n)的时间复杂度,只不过额外增加了一个对象,所以这也是一个典型的空间换时间的例子,但是也不要担心,ids用完之后垃圾回收机制会把他回收的。

# 3. 不要轻易使用setInterval

# 3.1 setInterval的弊端:

  1. 不会关心回调函数是否还在运行 在某些情况下,函数可能需要比间隔时间更长的时间去完成执行。比如说是用setInterval每隔5秒对远端服务器进行轮询,网络延迟,服务器无响应以及其他因素将会阻止请求按时按成。结果会导致返回一串没必要的排成队列请求。

  2. 忽视错误 因为某些原因,setInterval调用的代码中会出现一个错误,但是代码并不会中止执行而是继续执行错误的代码。

  3. 缺乏灵活性 除了前面提到的缺点之外,期望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')
1
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实现倒计时,消除时间误差(待填)

# 4. 监听页面唤醒

最近更新时间: 11/12/2020, 6:06:22 PM