为啥要用rxjs叻?
答:为了有更好的体验!开面包车也是开,开奔驰宝马也是开。rxjs能做的事情,用其他方式都能做。但是!rxjs是干净的,统一的,富有想象力的代码。
比如开个定时器,我希望某个条件下,这个定时器要关掉不执行,那就timer(3000).takeUntil(中断信号)。这样关闭定时器这个行为,就能承接给另一个异步信号来触发,这是解耦
比如在一个页面发出一个信号,然后再到其他页面后能消费这个信号,那我就可以用replySubject缓存信号,这样后面打开的页面也能消费到前面发出的信号。诶,那要提问了,我写个变量放到公共的地方,前一个页面我改这个变量,等打开后面的页面,我检查这个变量,发现是指定的值,我就执行相应的逻辑,不行嘛?还有,前面的例子,我就想用clearTimeout,不行嘛?不行嘛?那当然是行,完全可以。
但是,这种条件式的代码,如果能拥有统一的风格的话,当你在看到这样风格的代码的0.00001秒的时刻,你就能知道这里是一种异步的订阅的代码,然后在0.00002秒的时刻,你就猜到这个代码为什么要这么写了,然后在0.00003秒的时刻,你就知道怎么改这段代码了。这就是风格,带来的好处。
rxjs不仅是风格漂亮,功能也很强大
甚至可以用rxjs在angular优雅实现vue的watcher功能
在angular中如何深度watch一个对象? (juejin.cn)
然后,promise和rxjs怎么对比呢?其实有这样想法是错误的,因为这二者关注的层面是不同的。promise关注的是优化异步回调,就是把异步回调拆出来。rxjs关注的是,消息订阅的前置处理,是面向切面编程的味道,同时带来风格上的丝滑体验。吃面包也有风味的不是,我就喜欢吃柔软的牛角包和柔软拉丝的吐司面包,或者硬核的菠萝包。咱们写代码也像做面包一样,你想写出什么风味的面包?咳咳,过了。
-
observable 其实可以理解为一个拟定的行为,而一旦被observer订阅,就执行一遍,仅此而已。这个拟定的行为里,可以包含同步代码也可以包含异步代码,但是从订阅到触发,这个过程就一定是同步触发的。其实新建信号很少用这个observable,而一般会在进一步封装别人提供的信号时使用。
-
subject,简单理解成一个信号发射器,你想要发信号,那就用subject吧!subject实际继承自observable,但相比observable而言,subject内部还维护了一个订阅者队列,每当subject有信号发出,就会遍历队列,推送信号。那么,显然,subject其实就是中间人设计模式。
-
那么问题来了,为什么subject需要多维护一个观察者队列?其实仔细想想就知道了,那必然是好处多多。俗话说,软件设计上,总有这么个定律,如果解决不了问题,那就加一层。所以这首先这么设计,是为了应用上的问题。上面说了observable其实就是一个拟定的行为,也就是说一旦被创建,obseravble的行为也被确定了,同时将来发射的信号也被确定,无论是发射时间和数量都被确定。所以这就导致,任何订阅者无论何时何地订阅,得到的消息都是一致的。但我们不想这样,我们希望灵活一点。比如今天晚来的同学,没鸡腿。而subject其实就是饭堂的打饭阿姨,他做的事情就是去订阅别的obserable(大厨叔叔),然后转发给自己队列的observers。这就是为什么,subject既是observable又是observer的原因。
-
既然subject 是被设计为充当打饭阿姨,自然就可以干点超出你想象的事了。小伙汁,阿姨很中意你哇,加菜。咳咳,不是,比如组播、缓存。
-
也就是说,subject多了中间一层,变得更强了
-
先说组播:如果没有subject,如果有两个observer想去订阅一个observable,可想而知,你的两个observer永远都会完整接收到observable发出全部的通知,无论是否同时去订阅。那么此时需求就是,希望从严格的时间意义上,晚订阅的observer,不要接收到前面的信号,而只有早订阅的能接收到。这时候,就可以用subject订阅这个observable,然后两个observer订阅这个subject就好了。那么结果就是,晚来的observer不会接受到前面的信号。
-
缓存:诶,要提问了,我觉得这个阿姨太笨了,明明后厨还有鸡腿,她不端出来!那我们就来升级下subject的配置吧。我们有时候希望缓存一下信号,给后来的observer。这样好,什么时候过来都有鸡腿了。为了得到这个缓存鸡腿,我们可以用behaviourSubject和replySubject。前者behaviourSubject无论何时被订阅总会返回一个最新信号值,就像保存快照一样,也因此,保持为了一致,behaviourSubject在实例化时就必须传入一个初始值;而replySubject实例化时接收的参数是一个整数n,意思就是将来有n信号会被缓存起来。那behaviourSubject的区别和replaySubject(1)又是什么?其实也很容易想到,如果observable源真的没有信号发出,那么前者也会给一个初始信号给订阅者,而后者不会。
-
但是可能有的小朋友又要问了,我召唤的这个阿姨只能在食堂打饭吗?那当然不是了。换个美团小哥来说。比如你是饭店老板,有好多饭要送,那就new一个最原始的subject→外卖小哥,这时你只需要手动subject.next(外卖)派给这个美团小哥,他就管送就好了,给啥送啥,5星好评。因为subject没有订阅observable,信号传递始终是透明的,也不用怕小哥半路啃你一个鸡腿。注:此时subject不需要订阅任何信号源。不同于饭堂阿姨,饭堂阿姨可以自行决定是否去后厨端鸡腿,而外卖小哥则没有自主决定的权力,有什么就要送什么。
-
再说说observable的设计方式,有一个更深层的意义,就是渐进式取值。想象es6的高阶函数filter、map在链式调用时,其实数组每次都会遍历一次,赤裸裸的性能损失。要提问了,喂,你就不能filter的时候,顺便吧map也做了吗?es6不行,rxjs可以,每个信号都会走完filter,到map,最后进入subscribe被处理,才会处理下一个信号。
-
scheduler:通过observerOn可以改变observable的行为触发策略,queue同步,asap异步,async宏任务,animationFrame浏览器下一次重绘前。除了改变observable,实际上还可以直接使用queue.schedule(_=>{}) 的方式来写。其实就是可以把一些同步的东西改成异步的,其实也没怎么用过。
关于sechduler,找了下官网的例子,改了下(例子说明了,本来got value会被同步执行的,但现在变成异步的了)
官网例子没有框住的123
可以看到,实际上action仍然是同步触发的,而真正被异步处理的,其实是每一次的next广播
最后说一下pipe的意义
- 这是纯函数设计,可以减少操作符副作用,易于库的设计和拓展,使库更健壮,bug更少。
- 旧的写法为了支持链式调用,就一定要不断地传递上下文。但为了使操作符更加符合纯函数设计,为了不在操作符函数中传递上下文,所以需要套一层壳在外面维护上下文,这就是pipe。其实你去看看java的流式api源码,也能看到但凡为了支持链式调用,就一定会需要操作符处理上下文,当然,这是种取舍。
- 当操作符从Observable.prototype中独立拆分出去后,用import语法来引入操作符,就能支持更好的树摇优化,因为可以清晰地跟踪代码使用了哪些操作符,而不是所有的操作符都挂在Observable上一次性引入。