稀土掘金 稀土掘金

彻底理解RxJS里面的Observable 、Observer 、Subject

最近闲来无事,常常重读Angular官方文档,颇能发现些有趣的地方。让我想起海澜之家的广告词:每次都有新体验。

Observable和Observer

关于RXJS的基础概念,observableobserver,我们好多次搞得头晕眼花。

其实,看下面这简简单单的一行代码就懂了它们的关系:

observable.subscribe(observer);

observable是数据源头,是生产者,是待订阅者,通过subscribe方法可以被订阅,而observer 是观察者,数据使用者,数据消费者。

这句代码返回的是一个订阅对象,代表着一个订阅的发生,一个订阅的过程,一个Subscription对象的实例化。

observer其实是一个有三个回调函数的对象,每个回调函数对应三种Observable 发送的通知类型(nexterrorcomplete)。回调函数不必每次都提供三个。如果我们只提供了一个回调函数作为参数,subscribe会将我们提供的函数参数作为next的回调处理函数。

observable.subscribe()是一个Subscription对象。Subscription就是表示Observable 的执行,可以被清理。这个对象最常用的方法就是unsubscribe方法。同时,它还有 add方法可以使我们取消多个订阅。

我有一个比喻可以很好的理解这种订阅关系:现在有一家牛奶生产商,它们家的牛奶质优价廉,鲜美醉人。它在电视上发布广告,所有人都可以打它们的电话订奶。这个时候,牛奶商就是Observable,市民就是Observer。如果市民打电话(subscribe)给牛奶商,它们就会在牛奶商送奶(next)成功的时候收到牛奶,至于怎么喝就是自己的事情了,而市民是不关心牛奶是怎么生产和如何送过来的(比如数据库,HTTP过程的TCP/IP协议,握手过程等)。送奶过程可能会遇到意外导致送奶失败(error),而成功之后,牛奶商会把这次送奶标记为已送达(complete)。

这些都是基础中的基础,即使有时候我们被它搞得头晕眼花,但是我们也大概知道它们的意思。而下面这个Subject,周围很多人感觉都不太理解。

我们常常搞不清楚RXJS里面Subject的概念以及何时使用它。其实,在官方文档的最开始,简单的HERO教程里,早就有这么一段:

Subject其实是一个很有用,很优雅的东西。在RXJS官方文档,我们可以看到关于Subject的介绍:

后面还有这么一句话:Subjects 是将任意 Observable 执行共享给多个观察者的唯一方式。 这段官方文档的翻译显然有些生涩。其实,简单的说,Subject既是Observable,也是观察者(可以多个)。

我们知道,对于Observable来说,每个观察者有自己的订阅者的独立执行。什么意思呢?比如说,有一个人订奶,就要有一个送奶的过程发生;有一个Service里面的Get请求被订阅,就会发生一个Http请求。

这种模式在我们的某些情况下不太适合,比如说,输入框input change的过程,可能是毫秒之间,如果采用这种方式,我们需要创建多少个Subscription对象?或者牛奶商双十一做促销活动,突然一秒钟就有一个订单产生,我们如果还采用一个一个送,需要多少个送奶执行过程?

聪明的你应该想到了,那么一次送多份奶呗。 这就是RXJS官方文档说的:Subject 像是 Observable,但是可以多播给多个观察者。Subject 还像是 EventEmitters,维护着多个监听器的注册表。

再来看Hero教程里面的应用:

private searchTerms = new Subject<string>(); 
search(term: string): void { 
   this.searchTerms.next(term); 
}

searchTerms现在就是一个源源不断的能发出输入框值的流。即可以不断的发出值,又可以使用subscribe源源不断的获得值。而这一切,通过Subject变得非常简单。

Subject还有三个比较常用的子类:ReplaySubject,AsyncSubject,BehaviorSubject。它们代表什么意思?之间有什么区别呢?

Subject

代码例子:

import { Subject, BehaviorSubject, ReplaySubject, AsyncSubject } from 'rxjs';

let subject1: Subject<number> = new Subject<number>();
subject1.next(100);
subject1.subscribe(res=>{
      console.log('SubjectA:'+res);
 })
subject1.subscribe(res=>{
      console.log('SubjectB:'+res);
 })
subject1.next(200);
subject1.next(300);

执行结果:

 SubjectA:200
 SubjectB:200
 SubjectA:300
 SubjectB:300

可见,Subject只有在订阅之后,才能收到数据源发出的值。 subject1.next(100)的时候,还没有被订阅,因此不会打印结果。假如我们想在订阅者创建之后,无论什么时候都能拿到数据, 这应该怎么办呢? 下面的就派上用场了。

BehaviorSubject

代码例子:

let subject2: BehaviorSubject<number> = new BehaviorSubject<number>(0);
subject2.next(100);
subject2.subscribe(res=>{
     console.log('Behavior-SubjectA:'+res);
});
subject2.next(200);
subject2.subscribe(res=>{
     console.log('Behavior-SubjectB:'+res);
});
subject2.next(300);

执行结果:

 Behavior-SubjectA:100
 Behavior-SubjectA:200
 Behavior-SubjectB:200
 Behavior-SubjectA:300
 Behavior-SubjectB:300

可见,BehaviorSubject会保存最新的发送数据,当被订阅时,会立即使用这个最新数据。 然后会继续接收新的值。Behavior-SubjectA发生订阅时候,当前值是100,所以会立即打印100,然后它收到了新发出的200;这个时候Behavior-SubjectB发生订阅了,它会立即打印subject2流的最新值200;然后A、B依次都接收到了300。

需要注意:BehaviorSubject必须设置默认值。因为它有一个最新值(当前值)的概念。那么我们如果想保存所有的数据,而不只是最新值,怎么办呢?

ReplaySubject

代码例子:

let subject3: ReplaySubject<number> = new ReplaySubject<number>();
subject3.next(100);
subject3.next(200);
subject3.subscribe(res=>{
      console.log('Replay-SubjectA:'+res);
});
subject3.next(300);
subject3.subscribe(res=>{
      console.log('Replay-SubjectB:'+res);
});
subject3.next(400);

打印结果:

 Replay-SubjectA:100
 Replay-SubjectA:200
 Replay-SubjectA:300
 Replay-SubjectB:100
 Replay-SubjectB:200
 Replay-SubjectB:300
 Replay-SubjectA:400
 Replay-SubjectB:400

ReplaySubject会保存所有值,然后回放给新的订阅者。

这里Replay-SubjectA订阅的时候,数据流里面有100,200,之后接收300;这个时候新的订阅Replay-SubjectB发生了,数据流里面有100,200,300,所以它会依次打印出来这些已经保存的值;然后A和B依次收到了400。

new ReplaySubject()参数表示存的个数,即bufferSize,例如,我们把其设置为1:

let subject3: ReplaySubject<number> = new ReplaySubject<number>(1);

这时打印的结果只会保存一个流里面的数据:

Replay-SubjectA:200
Replay-SubjectA:300
Replay-SubjectB:300
Replay-SubjectA:400
Replay-SubjectB:400

我们可以理解为,这些数据流全都会被保存下来,当有新的订阅发生时,像放电影一样回放给订阅者。

AsyncSubject

AsyncSubject 只有当 Observable 执行完成时(执行complete()),它才会将执行的最后一个值发送给观察者。

也就是说,它只会保存流里的最后一条数据,而且只会在数据流complete时候才会发送。

let subject4: AsyncSubject<number> = new AsyncSubject<number>();
subject4.next(100);
subject4.subscribe(res => {
    console.log('Async-SubjectA:' + res);
});
subject4.next(200);
subject4.subscribe(res => {
    console.log('Async-SubjectB:' + res);
});
subject4.next(300);
subject4.subscribe(res => {
    console.log('Async-SubjectC:' + res);
});
subject4.complete();
subject4.next(400);

执行结果:

 Async-SubjectA:300
 Async-SubjectB:300
 Async-SubjectC:300

可见,  数据流在complete之前,有100,200,300。最后一条是300。所有订阅者都只会收到最后一条300。而complete之后,自然不会再发送值了。

附加知识

最后:其实Subject还有一个子类AnonymousSubject,只是这个子类很少有使用场景。本着好奇的研究心态,我们来看看它到底是什么东西呢?

有时候我们想当然认为使用create可以实例化Subject,实际上,Subject.create() 会返回AnonymousSubject对象实例,而new Subject()会返回Subject对象实例。AnonymousSubject不会像一般的Subject一样订阅它自己。它会标记数据源,然后在订阅发生的时候,它会直接连接起数据源和观察者但是却不追踪我们创建的订阅的过程(即数据变化的过程)。 比如:

var timer$ = Rx.Observable.timer(1000, 2000);
var timerSubject = Rx.Subject.create(null, timer$);

var subscription1 = timerSubject.subscribe(n => console.log(n));
var subscription2 = timerSubject.subscribe(n => console.log(n));

setTimeout(() => timerSubject.unsubscribe(), 4000); //不生效

这里的subscription1实际上是直接订阅了timer$,所以它其实是不可unsubscribe的。

至于AnonymousSubject到底有什么应用场景,官方文档也没有介绍。

玻璃钢生产厂家宣威市玻璃钢雕塑批发南宁玻璃钢雕塑工厂上海大型商场美陈厂家供应上海户外商场美陈供应玻璃钢雕塑供应厂家绍兴玻璃钢南瓜屋雕塑玻璃钢雕塑加工厂家四川惠州led发光玻璃钢雕塑制作工业玻璃钢花盆怎么样内乡玻璃钢雕塑厂家玻璃钢雕塑用什么晋城玻璃钢卡通雕塑定制昭通市玻璃钢雕塑设计要多少钱商场室内美陈图片公园水景玻璃钢景观雕塑制作厂家玻璃钢蜘蛛侠雕塑订做商业街玻璃钢雕塑工厂玻璃钢和砂岩雕塑价格玻璃钢桃子雕塑厂家山西商场节庆美陈雕塑报价陕西创意玻璃钢雕塑泸州玻璃钢广场雕塑定制赣州步行街玻璃钢雕塑定做价格江西景观玻璃钢雕塑加工铸造玻璃钢卡通雕塑加工玻璃钢彩绘雕塑图片玻璃钢雕塑运 动人物萍乡多彩玻璃钢雕塑制作北京艺术商场美陈费用白银仿真玻璃钢雕塑多少钱香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化