「RxJS」全稱 「Reactive Extensions for JavaScript」, RxJS
是一個庫,它通過使用 observable
序列來編寫異步和基于事件的程序。本文帶你了解RXJS
中四種Subject
的使用。
介紹
Subject 是用于多播的Observable
,這意味著Subject
確保每個訂閱都獲得與訂閱者之間共享可觀察執(zhí)行完全相同的值。
在介紹它們之前,我們先來看一下四種Subject
與普通Observable
的區(qū)別:
Subject
Subject
其實(shí)是觀察者模式的實(shí)現(xiàn),所以當(dāng)觀察者訂閱Subject
對象時(shí),它會把訂閱者添加到觀察者列表中,每當(dāng)有接收到新值時(shí),它就會遍歷觀察者列表,依次調(diào)用觀察者內(nèi)部的next
方法,把值一一送出。我們先來看一下Subject
的使用方法:
上面例子就比較容易理解:
- 我們創(chuàng)建了一個
Subject
- 發(fā)出了一個值1,但由于此時(shí)并沒有訂閱者,所以這個值不會被訂閱到
- 創(chuàng)建了訂閱者 A
- 又發(fā)出一個值 2,這時(shí)候訂閱者 A 會接收到這個值
- 又創(chuàng)建一個訂閱者 B
- 最后發(fā)出一個值 3,這時(shí)候已經(jīng)訂閱的都會接收到這個值
BehaviorSubject
很多時(shí)候我們會希望Subject
能代表當(dāng)下的狀態(tài),而不是單純的事件發(fā)送,也就是說如果當(dāng)前有一個新的訂閱,我們希望Subject
能立即給出最新的值,而不是沒有回應(yīng)。這個時(shí)候我們就可以使用到BehaviorSubject
BehaviorSubject
繼承自Subject
,它具有存儲當(dāng)前值的特征。這表示你可以始終直接從BehaviorSubject
獲取到最后發(fā)出的值。請參閱下面代碼示例:
這段代碼做了那些工作呢?
- 我們首先創(chuàng)建了一個
BehaviorSubject
的實(shí)例behavior$
,并在實(shí)例化時(shí)傳入初始值 0。 - 然后我們訂閱了這個這個實(shí)例
behavior$
,由于BehaviorSubject
的特點(diǎn)是把最新的值發(fā)布給訂閱者,訂閱者 A 會得到初始值 0,所以就會打引出訂閱者 A,訂閱值為:0 behavior$
使用內(nèi)置的next
方法發(fā)出一個新的值 1,這時(shí)候訂閱者 A 將會收到新的值,打印出訂閱者 A,訂閱值為 1- 我們增加一個訂閱者 B,這時(shí)候它會得到最新的值 1,所以打印結(jié)果為訂閱者B,訂閱值為 1
- 最后我們再一次調(diào)用
behavior$
的next
方法,由于我們之前已經(jīng)訂閱了兩次,所以訂閱者 A 和訂閱者 B 都會接收到新的value
(推薦教程:JavaScript教程)
ReplaySubject
有時(shí)候我們創(chuàng)建一個Subject
,但又想在每次新的訂閱時(shí),它都會重新發(fā)送最后幾個值,這個時(shí)候我們就可以用到ReplaySubject
。
ReplaySubject
可以將舊數(shù)據(jù)發(fā)送給新的訂閱者,這點(diǎn)很像是BehaviorSubject
,但是它還有一個額外的特性,它可以記錄一部分的observable
執(zhí)行,所以它可以存儲多個舊值并發(fā)送給它的新訂閱者。
創(chuàng)建ReplaySubject
時(shí),可以指定要存儲多少值以及要存儲多長時(shí)間。它的第一個參數(shù) bufferSize
指定了緩存的大小,默認(rèn)為 Infinity
,即緩存所有發(fā)出的值,是一個「空間限制」。我們還可以向其傳遞第二個參數(shù) windowTime
,指定緩存的「時(shí)間限制」,默認(rèn)為 Infinity
,即不限制值的失效時(shí)間。請參閱下面代碼示例:
這里發(fā)生了一些事情:
- 我們創(chuàng)建了一個
ReplaySubject
的實(shí)例replay$
,并指定我們只想存儲最后兩個值 - 我們創(chuàng)建了一個訂閱者 A
- 調(diào)用三次
replay$
的next
方法,把值發(fā)布給訂閱者。這時(shí)訂閱者 A 將會打印三次 - 現(xiàn)在就來體驗(yàn)
ReplaySubject
的魔力。我們使用replay$
創(chuàng)建了一個新的訂閱者 B,由于我們告訴ReplaySubject
,存儲兩個值,因此它將直接向訂閱者 B 發(fā)出這些最后的值,訂閱者 B 將打印出這些值。 replay$
發(fā)出另外一個值,這時(shí)候,訂閱者 A 和訂閱者 B 都接收到值的改變,打印出另外一個值
如前面所說的一樣,你還可以指定值在ReplaySubject
存儲的時(shí)間,我們來看一個例子
上面代碼中發(fā)生了那些事情呢:
- 我們創(chuàng)建了
ReplaySubject
,并指定它只存儲最后兩個值,但是不超過 100ms - 創(chuàng)建一個訂閱者 A
- 我們開始每 200ms 發(fā)出一個新的值。訂閱者 A 會接收到發(fā)出的所有值
- 我們創(chuàng)建一個訂閱者 B,由于是在 1000ms 后進(jìn)行訂閱。這意味著在開始訂閱之前,
replay$
已經(jīng)發(fā)出了 5 個值。在創(chuàng)建ReplaySubject
時(shí),我們指定最多存儲 2 個值,并且不能超過 100ms。這意味著在 1000ms 后,訂閱者 B 開始訂閱時(shí),由于replay$
是 200ms 發(fā)出一個值,因此訂閱者 B 只會接收到 1 個值。
有的同學(xué)看到這里,會感覺到ReplaySubject(1)
是不是就等同于BehaviorSubject
。但是,二者無論在概念上還是行為上,都是有所區(qū)別的。
首先概念上的區(qū)別是本質(zhì)的,ReplaySubject
只是緩存了最近的值,它仍然反映的是不斷有值產(chǎn)生的流(「多值」),而BehaviorSubject
反映的則是隨時(shí)間變化的值(「單值」)。因此,BehaviorSubject
需要傳入一個初始值,然后這個值將不斷變化,我們只能看見當(dāng)前的值。
在行為上,由于ReplaySubject
側(cè)重于緩存,那么當(dāng)它完成時(shí),并不會影響我們繼續(xù)觀測它緩存的值。我們來看下面這個例子:
ReplaySubject
在執(zhí)行完complate
時(shí),我們訂閱它仍然可以拿到緩存的值,而BehaviorSubject
在執(zhí)行完complate
時(shí),我們繼續(xù)訂閱它已經(jīng)沒有任何作用了。
AsyncSubject
雖然BehaviorSubject
和ReplaySubject
都存儲值,但AsyncSubject
的工作方式卻有所不同。AsyncSubject
是一個Subject
變體,其中僅Observable
執(zhí)行的最后一個值發(fā)送到其訂閱者,并且僅在執(zhí)行完成時(shí)發(fā)送(類似于rxjs/operators
里面的last
方法)。請參考下面的示例代碼:
這次沒有太多的事情發(fā)生。但是,讓我們看一下執(zhí)行步驟:
- 我們創(chuàng)建
AsyncSubject
的實(shí)例async$
- 我們通過訂閱者 A 進(jìn)行訂閱
async$
發(fā)出 2 個值,仍然沒有發(fā)生變化- 我們創(chuàng)建一個訂閱者 B
- 發(fā)出新的值,但是兩個訂閱者都沒有任何反應(yīng)
async$
執(zhí)行complate
完成,這時(shí)候?qū)⒆詈笠粋€值發(fā)送給所有訂閱者
從上面的代碼示例可以看出來AsyncSubject
會在執(zhí)行complate
后才送出最后一個值,其實(shí)這個行為跟 Promise
很像,都是等到事情結(jié)束后送出一個值。在 Promise
中,我們可以通過 resolve(value)
聲明任務(wù)完成,并將獲得的值發(fā)送出去,然后再通過Promise.then()
方法中處理得到的值。
(推薦微課:JavaScript微課)
以上就是關(guān)于RXJS
中四種Subject
的區(qū)別的相關(guān)介紹了,希望對大家有所幫助。