原文地址: https://segmentfault.com/a/1190000012669794

引言

  开发ngx(angular 2+)应用时,基本上到处都会用到rxjs来处理异步请求,事件调用等等。所以经常会使用Subject来处理源源不断的数据流,比如input text change, toast notification等等。
  这都要依赖于Subject本身既可以是Observable也可以是Observer,也就是说subject既可以作为一个数据源,也可以本身成为一组订阅者的代理
  但当处理更加复杂的业务需求时,仅仅用Subject可能无法满足要求,这个时候就考虑一下rxjs提供的其他Subject Class了, 例如BehaviorSubject ReplaySubject AsyncSubject, 接下来我们就来看一下他们跟Subject有什么区别,各自有什么特点,在什么时候更适合使用。

  [文中代码均使用typescript]

Subject

  首先我们来创建一个Rxjs Subject, 数据的类型是number

  let subject1: Subject<number> = new Subject<number>(); // (A)

  然后我们使用Subjectnext方法来emit(发射)1条数据

  subject1.next(100); (B)

  接下来对subject1创建两个订阅,在subscription中直接打印接受到的数据

   subject1.subscribe((res: number) => console.info("subjectA ", res));  // (C)
subject1.subscribe((res: number) => console.info("subjectB ", res));

  接下来我在发射两条数据

   subject1.next(200); (D)
subject1.next(300);

  好了,接下来我们就来看看console里面会打印出什么结果。
  也许有的同学会觉得结果是这样的,因为Subject可以接收源源不断的数据嘛,所以无论发射多少次数据,订阅者都能接收到。

//output
subjectA 100
subjectB 100
subjectA 200
subjectB 200
subjectA 300
subjectB 300

  这个结果不太对
  因为Subject的订阅者只有在订阅后,才能接收到数据源发射过来的值。
  所以在代码块C中, 订阅者在订阅数据源subject1之前, 无论代码块B执行多少次, 订阅者也只能收到代码C之后发射的数据。

  正确的结果应该是:

//output
subjectA 200
subjectB 200
subjectA 300
subjectB 300

  这种情况在项目里经常能遇到,有时候我明明从数据源发射一个数据,但在订阅者拿到的值却是undefined或者null, 这就是因为订阅者是在数据源发射之后创建的,自然无法接收到数据了。
  假如我们想在订阅者创建之后,无论什么时候都能拿到数据, 这应该怎么办呢? 那就要考虑使用BehaviourSubject了。

BehaviorSubject

  我们依旧使用刚才的例子, 创建一个BehaviorSubject, 默认值设为0. BehaviorSubject需要给个默认值
  然后发射一条数据100,创建一个订阅者,再发射一条数据200,再创建一个订阅者,最后发射一条数据300。
  代码如下:

let subject2: BehaviorSubject<number> = new BehaviorSubject<number>(0);
subject2.next(100);
subject2.subscribe((res: number) => console.info("behavior-subjectA ", res));
subject2.next(200);
subject2.subscribe((res: number) => console.info("behavior-subjectB ", res));
subject2.next(300);

  这个时候结果就应该是:

//output
behavior-subjectA 100
behavior-subjectA 200
behavior-subjectB 200
behavior-subjectA 300
behavior-subjectB 300

  由于BehaviorSubject是可以存储最后一条数据或者初始默认值的, 所以无论订阅者什么时候订阅到数据源subject2上, 都能接收到数据。
  所以针对订阅者behavior-subjectA, 他订阅的时候,数据流里最后一条数据是100, 他能立即接收到。 然后依次能接收到最新的数据200300
  针对订阅者behavior-subjectB, 他订阅的时候,数据流里最后一条数据是200, 他能立即接收到。 然后只能能接收到最新的数据300了。

  BehaviorSubject给予我们的便利就是,无论何时订阅到数据源,始终都能拿到最新的或者初始的数据,但也只能拿到一条数据,但当我们处理input text change事件时,需要拿到用户输入的所有字符,也就是数据流的所有数据,BehaviorSubject就无能为力了,这个时候我们考虑使用ReplaySubject了。

ReplaySubject

  我们依旧使用刚才的例子, 创建一个ReplaySubject, 发射两条数据100和200,创建一个订阅者,再发射一条数据300,再创建一个订阅者,最后发射一条数据400。
  代码如下:

let subject3: ReplaySubject<number> = new ReplaySubject<number>();
subject3.next(100);
subject3.next(200);
subject3.subscribe((res: number) => console.info("replay-subjectA ", res));
subject3.next(300);
subject3.subscribe((res: number) => console.info("replay-subjectB ", res));
subject3.next(400);

  控制打印的结果将是:

//output
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会存储数据流中的所有数据无论何时订阅到subject3,订阅者都能获取了订阅之前数据流里的所有数据,然后依旧获取到接下来获取的到的新数据。
  就像ReplaySubject类名的中Replay, 一旦订阅到数据源,就会将数据流像放电影一样重新放一遍给你。

  订阅者replay-subjectA订阅到subject3的时候,数据流里已经有了100和200, 接收并打印出来。
  最后打印新数据300和400.
  订阅者replay-subjectB订阅到subject3的时候,数据流里已经有了100,200,300, 接收并打印出来。最后打印新数据400.

  接下来就要说下最后一种Subject了,也就是AsyncSubject

AsyncSubject

  AsyncSubjectBehaviorSubject`ReplaySubject`有些类似,但不同的是AsyncSubject只会存储数据流里的最后一条数据, 而且只有在数据流complete时才会将值发布出去
  AsyncSubject主要是用来处理异步操作,当数据源是异步请求或者事件处理时,可能会发射出很多数据,如果我们只希望数据源的异步操作完成的时候,订阅者才能接收到值,这个时候就可以使用AsyncSubject了。
  接下来我们看个例子,
  创建一个AsyncSubject, 然后发射数据,创建订阅者,再发射数据。。。

let subject4: AsyncSubject<number> = new AsyncSubject<number>();
subject4.next(100);
subject4.next(100);
subject4.subscribe((res: number) => console.info("async-subjectA ", res));
subject4.next(300);
subject4.subscribe((res: number) => console.info("async-subjectB ", res));
subject4.next(400);
subject4.subscribe((res: number) => console.info("async-subjectC ", res));
subject4.complete();
subject4.subscribe((res: number) => console.info("async-subjectD ", res));
subject4.next(500);

  最后的结果就应该是:

 
//output4
async-subjectA 400
async-subjectB 400
async-subjectC 400
async-subjectD 400

  由于subject4AsyncSubject, 只有在complete的时候才会向订阅者publish数据,而且只publish最后一次数据,所以无论订阅者何时订阅数据源,都可以接收到最后一次数据。
  但为什么没有打印出500呢,因为数据源已经complete了,你就无法再发射新数据了。

总结

  最后来总结一下四种Subject的特点,理解好的各自的特点,在项目开发中可以处理很多棘手的需求,同时也会避免很多问题的发生。

【Rxjs】 - 解析四种主题Subject的更多相关文章

  1. 【SQL】四种排序开窗函数

    一 .简单了解什么是开窗函数 什么是开窗函数,开窗函数有什么作用,特征是什么? 所谓开窗函数就是定义一个行为列,简单讲,就是在你查询的结果上,直接多出一列值(可以是聚合值或是排序号),特征就是带有ov ...

  2. IOS中Json解析的四种方法

    作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有的json代码格式比较混乱,可以使用此“http://www.bejson.com/”网站来进行JSON格式化校验 ...

  3. 四种生成和解析XML文档的方法详解(介绍+优缺点比较+示例)

    众所周知,现在解析XML的方法越来越多,但主流的方法也就四种,即:DOM.SAX.JDOM和DOM4J 下面首先给出这四种方法的jar包下载地址 DOM:在现在的Java JDK里都自带了,在xml- ...

  4. [EventBus源码解析] 订阅者处理消息的四种ThreadMode

    前言 在前面,我们探讨了如何在自己的代码中引入EventBus,进行基本的事件分发/监听:对注册观察者与事件发送的过程进行了浅析.从之前的学习中,我们了解到,EventBus一共有4种onEvent方 ...

  5. 解析XML的四种方式

    四种操作xml的方式: SAX, DOM, JDOM , DOM4J的比较 1. 介绍 1)DOM(JAXP Crimson解析器)         DOM是用与平台和语言无关的方式表示XML文档的官 ...

  6. Android自定义控件:进度条的四种实现方式(Progress Wheel的解析)

    最近一直在学习自定义控件,搜了许多大牛们Blog里分享的小教程,也上GitHub找了一些类似的控件进行学习.发现读起来都不太好懂,就想写这么一篇东西作为学习笔记吧. 一.控件介绍: 进度条在App中非 ...

  7. XML解析——Java中XML的四种解析方式

    XML是一种通用的数据交换格式,它的平台无关性.语言无关性.系统无关性.给数据集成与交互带来了极大的方便.XML在不同的语言环境中解析方式都是一样的,只不过实现的语法不同而已. XML的解析方式分为四 ...

  8. JAVA解析XML的四种方式

    java解析xml文件四种方式 1.介绍 1)DOM(JAXP Crimson解析器) DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准.DOM是以层次结构组织的节点或信息片断的集合.这 ...

  9. java解析xml文件四种方式

    1.介绍 1)DOM(JAXP Crimson解析器) DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准.DOM是以层次结构组织的节点或信息片断的集合.这个层次结构允许开发人员在树中寻找 ...

随机推荐

  1. idou老师教你学Istio05: 如何用Isito实现智能路由配置

    要介绍istio请求路由,我们不由得先从pilot 和 envoy开始谈起. 在服务网格中,Pilot管理和配置所有的envoy实例.在pilot中,你几乎可以配置所有的关于流量导向规则及其他故障恢复 ...

  2. 开启aix SFTP日志 是否和链接SFTP有关呢

    1.修改SSH配置 vi /etc/ssh/sshd_config 在sftp配置处添加-l INFO -f AUTH Subsystem sftp /usr/lib64/ssh/sftp-serve ...

  3. 求 无向图的割点和桥,Tarjan模板

    /* 求 无向图的割点和桥 可以找出割点和桥,求删掉每个点后增加的连通块. 需要注意重边的处理,可以先用矩阵存,再转邻接表,或者进行判重 */ const int MAXN = 10010; cons ...

  4. Linux下安装LoaderRunner LoaderGenerator

    Linux负载机上安装LR的理由: 1.windows系统,基本上每个vuser会消耗2-5M的内存,单机300-500或者更多的虚拟用户时,CPU占用率已经较高,此时整个测试环境的测试系统的瓶颈实际 ...

  5. 一步步实现ArcMenu效果

    先来看一下最终要实验的效果: 是不是跟国外的一款Path的菜单效果类似,这里的动画采用补间动画去实现,正而操练一下补间动画. 布局和子视图的测量处理: 新建一自定义View继承ViewGroup: 然 ...

  6. virtualBox中有线和无线两种情况下centos虚拟机和本地机互ping的方案

    之前写微信点餐系统的时候,刚开始是无线连接,然后每次进去虚拟机ip和本地ip都会改变,所以每次都需要配置一下nginx,还有本地的路径.之后换有线连接,就研究了一下桥接模式有线情况下虚拟机静态ip设置 ...

  7. BZOJ 1116 [POI2008]CLO-Toll 并查集

    如果一个连通块是一个树的形态,则不合法,否则合法. 用并查集判断一下即可. #include <bits/stdc++.h> #define N 100005 #define M 2000 ...

  8. C++全局变量的定义和声明

    编译单元 编译分为两个步骤: 第一步:将每个.cpp或.c和相应的.h文件编译乘obj文件(包含预编译,汇编.编译) 第二部:将obj文件进行Link,生成最终的可执行文件 根据该阶段错误大致可分为两 ...

  9. 【线性代数】3-6:四个子空间的维度(Dimensions of the Four Subspaces)

    title: [线性代数]3-6:四个子空间的维度(Dimensions of the Four Subspaces) categories: Mathematic Linear Algebra ke ...

  10. ImageSharp跨平台图片处理

    添加nuget引用 SixLabors.ImageSharp 和SixLabors.ImageSharp.Drawing 暂时只实现了缩略图..<pre>using SixLabors.I ...