Skip to content

13.实时推流和渲染优化

🎯 面试话术(2分钟版)

【说人话场景】 "这是做一个实时停车场监控大屏,比如一个城市有几十个停车场,每个车位都有传感器,哪个车位被占了、哪个空了,要实时显示在地图上。

一开始的做法是后端每隔几秒就把所有车位状态发过来,前端收到就画。结果车位多了以后(比如几千个),页面直接卡死,浏览器风扇狂转。"

【你做了什么 - 三步走】

第一步:别传全部,只传变化的(对应简历里的'差量更新') "我跟后端商量,改成**'谁变了传谁'**。比如 1000 个车位里只有 5 个车位状态变了,就只传这 5 个的 ID 和状态,而不是把 1000 个都发一遍。数据量从几百 KB 降到几 KB,手机也能流畅看了。"

第二步:别来一条画一条,攒一波一起画(对应'消息聚合') "一开始是 WebSocket 来一条消息,我就调用 ECharts 画一次。但传感器有时候一秒发几十条,ECharts 画不过来,就越积越多,页面越来越卡。

后来我加了节流(throttle):就像打游戏有帧率一样,我固定每 100 毫秒画一次。这 100 毫秒内收到的所有变化,我先存到一个数组里,到点了统一交给 ECharts 画一次。从画 50 次变成画 1 次,立马就不卡了。"

第三步:看不见的就不画(简单提一下,显得有思考) "如果地图缩得很小,很多车位在屏幕外面,我也先不画,等用户放大地图到这个区域时再渲染,省点性能。"

【成果】 "这样改完后,即使高峰期一秒收到几百条消息,页面也能保持流畅,不会卡顿。老板看大屏演示的时候,车位状态基本是实时变化的,没有延迟感。"


🔍 如果面试官追问(防身用)

Q:具体怎么实现"攒一波一起画"?代码大概什么样?

大白话回答: "就是用 setInterval 或者 requestAnimationFrame 做个定时器,比如每 100ms 执行一次。WebSocket 收到数据先 push 到一个临时数组里,等到 100ms 到了,把数组里的数据一次性塞给 ECharts。

大概长这样:

javascript
let cache = []; // 先存这里

// WebSocket 收到消息
ws.onmessage = e => {
  cache.push(e.data); // 先不画,存起来
};

// 每 100ms 画一次
setInterval(() => {
  if (cache.length > 0) {
    myChart.setOption({
      series: [{ data: cache }],
    });
    cache = []; // 画完清空
  }
}, 100);

Q:怎么知道哪些车位变化了?后端配合做了什么?

大白话回答: "后端那边维护了一个'上一次的快照',每次对比,只把不一样的字段发给我。比如就发 {车位ID: 'A123', 状态: '占用'},而不是发整个停车场的 JSON。

如果断网重连了,前端会告诉后端'我最后收到的是第几条',后端把中间漏的补发过来,或者干脆全量发一次保底。"

Q:如果 100ms 内同一个车位变了两次怎么办?

大白话回答: "这个确实要注意。我会用对象的 key 去重,比如用 Map 存,同一个车位 ID 多次变化,只保留最后一次的状态。不然会出现'明明已经开走了,又显示被占用'的闪烁 bug。"


💡 给两年经验的小贴士

可以说这些(显得踏实):

  • ✅ "一开始直接 setOption 很卡,后来查文档发现可以 lazyUpdate: true"
  • ✅ "和后端约定好了数据格式,只传 diff"
  • ✅ "用了防抖/节流限制渲染频率"
  • ✅ "用 Map 做了去重,避免重复渲染"

万一被问深了,甩锅给业务: "因为当时项目周期比较紧,我用了比较务实的方案。如果数据量再大一个数量级,我可能会考虑用 Canvas 直接画,或者上 WebGL,但目前这个方案已经能满足需求了。"

潜台词:我知道有更牛的方案,但我会根据业务选合适的,不 over-engineering)

Released under the MIT License.