Rxjs 中的 fromEvent subscribe 背后的故事

147 阅读3分钟

为什么单个 fromEvent 实例订阅多个事件时间反而还长

image.png

场景

在性能优化过程中,发现一个组件初始化时的卡顿问题,通过 Performance 分析工具定位到主要的性能消耗点。进一步分析后发现,问题源自组件初始化时订阅了 wheel 事件。单个组件加载时影响不大,但在大量渲染场景下,因事件监听的累积导致了丢帧和性能开销过高。

image.png

由于数据滚动采用虚拟滚动列表,组件的初始化和销毁逻辑较为频繁。如果每个单元格都独立订阅 wheel 事件,滚动时不仅触发大量事件处理,还可能在组件未销毁的情况下持续触发 wheel 逻辑,进一步加剧了性能问题。

解决方案

最初的想法是写一个公共的工具方法只创建一个 fromEvent(document, "wheel") 的事件流,订阅的时候直接从工具中获取这个事件流。好比我测试图中的方案 3。改造后验证了一下发现还是很卡,感觉跟没有优化一样。

之后我就单独写了一个测试的方案来验证一下:

  1. 循环 10000 次通过document.addEventListener 来订阅
  2. 循环 10000 次通过 fromEvent(document,"click")来订阅
  3. 单独创建一个 fromEvent(document, "click")然后订阅10000次
  4. 单独创建一个 fromEvent(document, "click")然后订阅1次,再创建一个 subject, 最后订阅 10000 次这个 subject, 当document事件流发射时再tapsubject中,让 subject 来分发这个事件流

通过图中的表现第四种方案是使用时间是最短的,那毋庸置疑最后的解决方案我采用的是第四种。能提升 99%渲染效率。

当然第四种方案有一定的缺陷会在下面说到,请继续阅读 👇

高频订阅 fromEvent 为什么慢

有没有跟我一样好奇的朋友,其实第三种方案与第四种方案都是 subscribe 了10000次,那为什么性能差距这么大。

其实我们可以深入探索一下 rxjs 中的 fromEvent 这个方法到底做了什么。

image.png

仔细看一下他的源码还是很清晰明了的。其实当你创建一个事件流的时候直接给你放回了一个 Obervable 什么也没有干,并没有去给你在document上真正的注册事件,而是当你 subscrible 的时候去执行 doSubscribe,这个时候才会去注册一次 addEventLinstener。也就是你 subscrible n 次那么他就会 addEventLinstener n次 所以这也就是为什么第三种方案优化了跟没有优化一样的根本原因。

由此看来第四种方案,只是 subject obervable 订阅了10000次,内部呢是往他的 observers队列中 push了10000个回调。所以性能才会很快。

哦豁~~ 解谜了 😏

总结

第三种方案:当你每订阅一次他内部都会获取一下 dom 并且做 addEventLinstener的绑定操作。所以还是相当于方案一直接还是 document.addEventListener 了1000次,在这个基础上还做了一些自身的逻辑导致耗时增加。

第四种方案:就是纯属事件订阅(不是dom的事件订阅哦😜),订阅的时候往obervabes里面 push 回调,当next的时候会从 obervabes中挨个的触发。所以就避免了dom的高频订阅操作。 他是无法对事件冒泡或捕获做还有 once 做对应的配置。可以根据当前场景来判断是否需要事件机制来选择适合你的方案。