RXJS
纯函数:
- 函数的执行过程完全由输入参数决定,不接受除参数之外的任何数据的影响。
- 函数不会修改任何外部状况,比如修改全局变量或传入的参数对象。
函数副作用:指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量),修改参数或改变外部存储。
**响应式编程:**响应式编程或反应式编程是一种面向数据流和变化传播的声明式编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
**数据流:**数据在系统内传播的路径,表示在一定时间范围内发生的一系列事件。任何东西都可以是一个Stream:变量、用户输入、网络响应、定时器、数据结构等。
**变化传播:**在数据流传播的过程中,可能会有以下事件去组合、创建、过滤这些Streams,从一个旧的Stream映射成一个新的Stream。不需要去轮询变化,而是对事件监听,在执行一个事件后,会自动做出相应的响应,这是变化传播。
RxJS中解决异步事件管理的基本概念:
-
**Observable(可观察对象):**将一个数据流看作一个可观察对象,表示这个数据流变化传播过程中发生的一系列事件的集合
单个值 多个值 拉取(pull) Function Iterator 推送(Push) Promise Observable -
**拉取体系:**JS中每个函数Function都属于拉取体系,函数来生产数据,消费者通过调用该函数的代码来从函数中获取单个返回值来对该函数进行消费,而迭代器Iterator则是消费者调用iterator.next()来获取多个返回值进行消费。
拉取过程中,生产者是一个被动的过程,在消费者请求调用自己时才产生数据,消费者是一个主动的过程,消费者自己来决定何时调用生产者来获取数据。
-
**推送体系:**Promise是常见的推送体系,将解析过的
resolve
值传给消费者注册过的一个回调函数。推送过程中,生产者是一个主动的过程,在生产者获取
resolved
值时,生产者可以决定何时把值推送给消费者,而消费者并不知道什么时候可以从生产者这里获取到值。在RxJS中observable
也属于推送体系,并且可以推送一个或多个值。//Function function foo(){ console.log('Hello '); return 'world'; } const x = foo(); console.log(x); const y = foo(); console.log(y); // 'Hello' 'world' 'Hello' 'world' //Observable const Fob = Observable.create((observer) => { console.log('hello'); observer.next('world'); }) Fob.subscribe(v => console.log(v)); Fob.subscribe(v => console.log(v)); // 'Hello' 'world' 'Hello' 'world'
Observable和function的区别在于:Observable可以随着事件推移返回(推送)多个值,这点函数是不行的。
-
创建Observable:Observable可以使用Observable.create来创建,但通常使用创建操作符来创建Observable
-
**订阅Observable:**订阅Observable像时调用函数,并提供接收数据的回调函数
observable.subscribe(value => { // do something })
同观察者通过subscribe调用同一observable数据不共享。
每一次调用,等于重新执行一遍函数。
-
**执行Observable:**可以传递三种类型的值:
- Next:推送一个值,可以是任意类型;
- Error:推送一个错误或异常
- Complete:推送一个(已完成)的信息,表明不会再发送任何值;
next()方法中的值代表要推送给观察者的实际数据,可以执行多次;
error和complete会在Observable执行期间至多执行一次,并且只会执行其中一个。
Observable.create(observer => { try{ observer.next(1); observer.next(2); observer.complete(1); observer.next(3);// 前面已经通知观察者完成了,所以这个值不会在发送 }catch(e){ observer.error(e); // 捕获到异常发送一个错误 } })
-
**销毁Observable执行:**Observable的执行可能是无限的,通常观察者希望在一个有限的时间里种植Observable执行,以避免浪费计算资源和内存消耗。类似于清楚定时器
//调用subscribe时,观察者会被附加到新创建的Observable执行中, //会返回一个对象,即Subscription(订阅) var sunscription = observable.subscribe(); //Subscription表示正在进行中的执行,调用unsunscribe()来取消Observable执行。 subscription.unsubscribe();
-
-
Observer(观察者)
Observer(观察者)是一组回掉函数的集合,每个回调函数对应Observable发送通知的类型:
next
,error
,complete
const observer = { next: () => {}, //观察者接收到next()信息执行的回调函数 error:() => {}, //观察者接收到error()信息执行的回调函数 complete:() => {} //接收到complete()信息执行的回调函数 } //observer中的观察者可能时部分的,没有提供某个回调,observable还是可以执行。 //方法1:将observer观察者传入subscribe observable.subscribe(observer); //方法2:subscribe按顺序(next,error,complete)传入三个回调函数 observable.subscribe((value) => {}, (error) => {}, () => {});
-
**Subscription(订阅):**Subscription是一个可清理资源的对象,代表Observable的执行。
基本用于使用unsubscribe来释放资源或取消Observable的执行。
-
Subject(主体)
Cold Observable(冷观察) / Hot Observable(热观察)
Observable对象是一个数据流,在一个时间范围内推送一系列数据。
在只存在一个observer的情况下很多简单,但是对于存在多个observer的场景,会复杂。
两个observer观察者A和B订阅同一个Observable对象,但是不是同时订阅的,第一个观察者A订阅N秒后,第二个观察者B才订阅这个Observable对象。并且这N秒期间,Observable已经推送了一些数据
**Cold Observable(冷观察):**已经推送给A的值,B订阅时从头开始获取Observable推送的数据
**Hot Observable(热观察):**已经推送给A的值,不需要给B,B只从订阅那一时间点接受Observable推送的数据就行。
RxJS的四种不同类型Subject:
Observable | Subject | BehaviorSubject | AsyncSubject | ReplaySubject |
---|---|---|---|---|
每次从源头开始将值推送给观察者 | 将值多播给已订阅的该Subject的观察者列表 | 把最后一个值(当前这)发送给观察者(需要一个初始值) | 执行的最后一个值发送给观察者 | 可以把之前错过的值发给观察者 |
-
不同类型的Subject
-
**BehaviorSubject:**BS有一个“当前值”的概念,它保存了发送给观察者的最后一个值(当前值),当有新观察者订阅时,会立即接收到“当前值”。
而如果用Subject,在观察者订阅时,之前已发送的值不会再发给观察者包括最近的一个值,后续再有值发送的时候,新注册的观察者才会接收到新的值。
var BSubject = new BehaviorSubject(0); //0是初始值 BSubject.subscribe({ next: (v) => console.log('observerA: ' + v), }); BSubject.next(1); BSubject.next(2); BSubject.subscribe({ next: (v) => console.log('observerB: ' + v) }); BSubject.next(3); // 输出: observerA: 0 //line3 :A订阅时立即收到当前值(初始值)0 observerA: 1 //line7 : BS推送新的值1,订阅者A接收到值1 observerA: 2 //line8 : BS推送新的值2,订阅者A接收到值2 observerB: 2 //line 10 : B订阅时立即收到变化后的当前值2 observerA: 3 //line 14: BS推送新的值3,订阅者A和B一起收到值3 observerB: 3
-
**AsyncSubject:**AS只有当Observable执行完成时【执行complete()】,才会将执行的最后一个值发送给观察者。
var ASubject = new AsyncSubject(); ASubject.subscribe({ next: (v) => console.log('observerA: ' + v), }); ASubject.next(1); ASubject.next(2); ASubject.next(3); ASubject.next(4); ASubject.subscribe({ next: (v) => console.log('observerB: ' + v) }); ASubject.next(5); ASubject.complete(); //输出 observerA: 5 observerB: 5
-
**ReplaySubject:**RS类似BS,它可以发送旧值给新的观察者,还可以记录Observable的执行的一部分,将Observable执行过程中的多个值回访给新的观察者。
var RSubject = new ReplaySubject(3); RSubject.subscribe({ next: (v) => console.log('observerA: ' + v), }); RSubject.next(1); RSubject.next(2); RSubject.next(3); RSubject.next(4); RSubject.subscribe({ next: (v) => console.log('observerB: ' + v), }); RSubject.next(5); //输出 observerA: 1 // line 7: RS推送值1,订阅者A收到值1 observerA: 2 // line 8: RS推送值2,订阅者A收到值2 observerA: 3 // Line 9: RS推送值3,订阅者A收到值3 observerA: 4 // line 10: RS推送值4,订阅者A收到值4 observerB: 2 // line 12: 新的订阅者订阅RS observerB: 3 // 订阅时按顺序收到了RS缓冲的三个值 observerB: 4 observerA: 5 // line 16:RS推送值5,观察者A和B收到值5 observerB: 5
-
-
常用操作符
-
**Create:**create将 onSubscription 函数转化为一个实际的 Observable 。每当有人订阅该 Observable 的时候,onSubscription 函数会接收 Observer 实例作为唯一参数行。onSubscription 应该调用观察者对象的 next, error 和 complete 方法。
const source = Rx.Observable.create(((observer: any) => { observer.next(1); observer.next(2); setTimeout(() => { observer.next(3); }, 1000) })) // 方式一 source.subscribe( { next(val) { console.log('A:' + val); } } ); // 方式二 source.subscribe((val) => console.log('B:' + val)); // A:1 // A:2 // B:1 // B:2 //- 1s后: // A:3 // B:3
-
**From:**从一个数组、类数组对象、
Promise
、迭代器对象或者类Observable
对象创建一个Observable
。该方法就有点像Js
中的Array.from
方法(可以从一个类数组或者可迭代对象创建一个新的数组),只不过在RxJS
中是转成一个Observable
给使用者使用。var array = [10, 20, 30]; var result = from(array); result.subscribe((x) => console.log(x)); // 10 // 20 // 30
-
of
:与From差不多,在使用时是传入一个一个参数来调用的,有点类似js中的concat方法。会返回一个Observable,它会一次将你传入的参数合并并将数据以同步的方法发出。var source = of(1,2,3); source.subscribe((x) => console.log(x)); // 1 // 2 // 3
-
debounceTime
:功能与防抖函数差不多,只有在特定的一段时间经过后并且没有发出另一个源值,才从Observable
中发出一个值。假设一个数据源每隔一秒发送一个数,而我们使用了
debounceTime
操作符,并设置了延时时间,那么在数据源发送一个新数据之后,如果在延时时间内数据源又发送了一个新数据,这个新数据就会被先缓存住不发送,等待发送数据之后并等待延时时间结束才会发送给订阅者。不仅如此,在延时时间未到的时候并且已有一个值在缓冲区,这个时候又收到一个新值,那么缓冲区就会把老的数据抛弃放入新的,然后等待延时时间到达然后将其发送。//RxJS 7.5.5版本 const source01 = interval(1000).pipe(take(3)); const result02 = source01.pipe(debounceTime(2000)); result02.subscribe((x) => console.log(x)); // 程序启动之后的前三秒没有数据打印,等到五秒到了之后,打印出一个2,接着就没有再打印了 // 数据源会每秒依次发送三个数0、1、2,由于我们设定了延时时间为2秒,那么也就是说,我们在数据发送完成之前都是不可能看到数据的,因为发送源的发送频率为1秒,延时时间为2秒,也就是说 除非发送完,否则不可能满足发送源等待两秒在发送新数据,每次发完新数据之后要等2秒之后才有打印,所以不论该数据源发送了多少个数,最终订阅者收到的只有最后一个数。
-
take
:只发出源Observable
最初发出的N个值(N = count
)。主要用于空值至获取特定数目的值,跟interval
这种会持续发送数据的配合起来就能自主控制要多少个值。 -
skip
:返回一个Observable
,该Observable
跳过源Observable
发出的前N个值(N = count
)。假设发送了6个值,可以使用skip
操作符来跳过前多少个。const source = from([1, 2, 3, 4, 5, 6, 7]); const result = source.pipe(skip(3)); result.subscribe((x) => console.log(x)); // 跳过了前面3个数。 4 5 6 7
-
concat
:concat
和concatAll
效果是一样的,区别在于concat
要传递参数,参数必须是Observable
。concat
将多个observable
串接起来,前一个完成好了再执行下一个。const source1 = interval(1000).pipe(take(3)); const source2 = of(3); const source3 = of(3, 5); const example = source1.pipe(concat(source2, source3)); example.subscribe({ next: (v) => console.log(v), error: (err) => { console.log('Error: ' + err); }, complete: () => { console.log('complete'); }, });
-
-
merge/combine合流
合流两种方式:
// merge --1----2-----3--------4--- ----a-----b----c---d------ merge --1-a--2--b--3-c---d--4--- // combine --1----2-----3--------4--- ----a-----b-----c--d------ combine --1a-2a-2b-3b-3c-3d-4d--
merge 的合流方式可以用在聊天室、多人协作、公众号订阅就可以通过这样的方式合流,最终按照顺序地展示出对应的操作记录。
在 Excel 中,通过函数计算了 A1 和 B2 两个格子的相加。这种情况下可以使用 combine 合流:
const streamA1 = Rx.Observable.fromEvent(inputA1, "input"); // 监听 A1 单元格的 input 事件 const streamB2 = Rx.Observable.fromEvent(inputB2, "input"); // 监听 B2 单元格的 input 事件 const subscribe = combineLatest(streamA1, streamB2).subscribe((valueA1, valueB2) => { // 从 streamA1 和 streamB2 中获取最新发出的值 return valueA1 + valueB2; }); // 获取函数计算结果 observable.subscribe((x) => console.log(x));