用《三体》的思想理解RxJS的本质

用降维打击的思维写程序

打个比方,没使用rxjs之前,写代码的程序员就好像《三体》中二维世界的生命无法感知三维这个维度一样,无法感知和操纵“时间”这个维度。而理解了rxjs后,程序员有了从“时间”这个第三维度去看代码的能力,业务逻辑对他来说是不随时间变化的(没有必要用异步的概念了!),仿佛时间静止一般。《三体》中领悟了操纵高维时空能力的女巫能从远处透过脑壳拿出敌人的大脑——别人惊讶她是如何做到的,而她只不过是看到了伸手直接拿了而已。

rxjs的本质

rxjs是比async/await更高层面的流程逻辑抽象。因为平时写代码时作者头脑中的视角是程序执行瞬间的视角,相当于时间静止,也就是缺少了时间流逝这个维度。rxjs是提供了更高层的逻辑抽象,把异步带来的时间因素给剥离到了程序流程外部,描述了一个动态的数据结构(事件序列,流,stream)。这个数据结构的内容是一个个事件,通过rxjs的算符(operator)对这些不断产生的事件的序列进行过滤、转换、组合。rxjs提供了很多与时间无关的、通用的高层抽象(事件序列操作算符API,),写代码时我们只需要使用这些API去表达——过滤 / 转换 / 组合等操作之间的关系,而无需关心具体操作的实现(隐藏在了rxjs库API的内部)。

使用rxjs的好处:

1. 事件产生逻辑与处理逻辑分离。

事件产生可能是click,websocket,或者是程序主动赋值,而处理逻辑一般是与事件如何产生无关的。

2. 事件处理逻辑被数据结构化,能自由组合,方便修改扩展

(函数式编程的思想)

我们考虑这么一个场景:
用户a, b, c, d都是远程的,他们处于同一个聊天窗口中
d需要负责把a和b发过来的数字相加,然后把结果与c发来的数字相乘之后,发到聊天窗口中

问:站在d的角度,这代码怎么写?
实际的业务逻辑其实很简单,就这么一句:
d = (a + b) * c

最大的麻烦是,这些异步过程把业务逻辑打散了,导致这个代码特别难写,也不清晰。

ES7的async/await可以解决用同步代码写法描述异步逻辑的问题,但本质上还是同步视角的思维,缺少对更复杂的操作的抽象封装

如果使用RxJS,我们可以把每个数据的变更定义成流,然后定义出这些流的组合关系:
最终代码如下:
http://codepen.io/xufei/pen/PGPYLK
const A = new Rx.Subject()const B = new Rx.Subject()const C = new Rx.Subject()const D = Rx.Observable .combineLatest(A, B, C) .map(data => { let [a, b, c] = data return (a + b) * c })D.subscribe(result => console.log(result))setTimeout(() => A.next(2), 3000)setTimeout(() => B.next(3), 5000)setTimeout(() => C.next(5), 2000)setTimeout(() => C.next(11), 10000)

为了简单,我们用定时器来模拟异步消息。实际业务中,对每个Subject的赋值是可以跟AJAX或者WebSocket结合起来,而且对D的那段实现毫无影响。
我们可以看到,在整个这个过程中,最大的便利性在于,一旦定义完整个规则,变动整个表达式树上任意一个点,整个过程都会重跑一遍,以确保最终得到正确结果。无论中间环节上哪个东西变了,它只要更新自己就可以了,别人怎么用它的,不必关心。
而且,我们从D的角度看,他只关心自己的数据来源是如何组织的,这些来源最终形成了一棵树,从各叶子汇聚到树根,也就是我们的订阅者这里,树上每个节点变更,都会自动触发从它往下到树根的所有数据变动,这个过程是最精确的,不会触发无效的数据更新。
所以,借助RxJS,我们实现了:
定义的时候,像getter一样清晰
执行的时候,像setter一样高效
每个环节是同步还是异步,都不影响代码的编写

参考来源:

  • https://github.com/xufei/blog/issues/36#issuecomment-246662343
  • https://fe.ele.me/let-us-learn-rxjs/
  • https://zhuanlan.zhihu.com/p/23305264

你可能感兴趣的:(用《三体》的思想理解RxJS的本质)