0. 前言
本文基於5.5.11版本!
本文基於5.5.11版本!
本文基於5.5.11版本!
Rx指的是響應式編程的實踐工具擴展——reactive extension,編程風格是響應式編程+函數式編程。Rxjs則是這種模式的js的實現,處理異步能力優秀,將異步操作抽象爲時間軸上的點。既可以當作像lodash那樣的工具庫來用,也可以用來統一管理數據流,他的出現解決了一些問題:
- 簡化了代碼
- 簡短且具有良好的可讀性
- 很好的處理異步
文檔看這裏
1. Observable
Rxjs核心概念就是Observable,一個可觀察對象,代表着接下來將要發生的一系列事件
Rx.Observable.create(observer => { observer.next(1); observer.next(2); observer.next(3); }) // 數據源 .map(x => x * 2) // 操作符 .subscribe(x => {console.log(x)}) // 訂閱數據 複製代碼
Observable作爲數據源產生數據,通過內部迭代器next一個個地產生數據,observer被動接受數據,經過一系列操作符處理,在下游用subscribe訂閱數據源最終結果進行操作。每次subscribe,create裏面的observer就會調用一次
2. 產生數據源
Observable.create:最原始的創建數據流的方法,其他方法其實是基於此方法的封裝,一般用其他的都可以滿足各種場景。每次最後subscribe都會執行一次create傳入的函數
Rx.Observable.create(observer => { observer.next(1); observer.next(2); observer.next(3); observer.error('err'); observer.complete(); observer.next(4); // 完成後不再監聽error或者next }).subscribe(console.log, err => {console.log('err', err)}, () => {console.log('complete')}) 複製代碼
創建同步數據流的基礎方法of比較常用,還有其他的各種功能的產生數據源的方法如:repeat、generate、range、never、throw等(cold observable)
異步數據流常用方法:interval、timer、fromPromise、fromEvent、ajax等 (後面三者是hot observable)
3. Hot & Cold Observable
- cold:subscribe後接受的是Observable產生過的所有的數據
- hot:subscribe後接受的是Observable被subscribe後產生的數據,之前的不算
// cold Rx.Observable.create(observer => { const producer = new Producer() // observer與producer關聯起來 }) // hot const producer = new Producer() Rx.Observable.create(observer => { // observer與producer關聯起來 }) 複製代碼
每一次被subscribe,會觸發Rx.Observable.create(observer)裏面的observer函數。cold類型的是每一次都是一個新的生產者,所以它會把所有的數據都訂閱。而hot類型是共享同一個生產者,所以只是訂閱以後的數據
來個例子:
先來一個生產者類:
class Producer { constructor(init) { this.num = init } connect(observer) { this.observer = observer return this } add() { setInterval(() => { this.num += 1 this.observer && this.observer.next(this.num) }, 1000) return this } } 複製代碼
hot:
const p = new Producer(10) const producing = p.add() const ob = Rx.Observable.create(observer => { producing.connect(observer) }) setTimeout(() => { ob.subscribe(x => console.log('p1',x)) }, 1000); setTimeout(() => { ob.subscribe(x => console.log('p2',x)) }, 3000); setTimeout(() => { ob.subscribe(x => console.log('p3',x)) }, 5000); 複製代碼
cold:
const ob = Rx.Observable.create(observer => { const p = new Producer(10) const producing = p.add() producing.connect(observer) }) setTimeout(() => { ob.subscribe(x => console.log('p1',x)) }, 1000); setTimeout(() => { ob.subscribe(x => console.log('p2',x)) }, 3000); setTimeout(() => { ob.subscribe(x => console.log('p3',x)) }, 5000); 複製代碼
cold類型的,所有的訂閱者都會從頭到尾接收到所有的數據(每一次訂閱都new一個生產者);而hot類型只接受訂閱後的產生的數據(所有的訂閱共享生產者)
5. 操作符
一個Observable對象代表一個數據流,對於實際應用上的一些複雜的問題,我們當然不直接subscribe數據流,而是先讓它經過一系列處理再subscribe。這個一系列的處理就是通過操作符來處理
- 接受上游的數據,經過處理流到下游
- 來自上游可能是源頭、可能是其他操作符甚至其他流
- 返回的是新的Observable,整個過程鏈式調用
操作符的實現
- 鏈式調用:返回this、返回同類實例
- 函數式編程:純函數、無副作用 那麼很容易推理出來,底層實現是返回新的Observable對象,而rx世界中一切產生數據源的方法都是基於create封裝,操作符返回的對象還具有subscribe方法。
Rx.Observable.myof = function(...args) { return new Rx.Observable.create(observer => { args.forEach(arg => { observer.next(arg) }) }) } Rx.Observable.prototype.mymap = function(fn) { return new Rx.Observable(observer => { this.subscribe({ next: x => observer.next(fn(x)) }) }) } Rx.Observable.myof(1,2,3).mymap(x => x*2).subscribe(console.log) 複製代碼
6. 彈珠圖
用彈珠圖看rx的數據流,特別形象而且容易理解,下面看一下例子:
const source1$ = Rx.Observable.interval(500).map(x => 'source1: ' + x).take(5) const source2$ = Rx.Observable.interval(1000).map(x => 'source2: ' + x).take(5) const source3$ = Rx.Observable.of(1, 2, 3) source1$.merge(source2$).concat(source3$).subscribe(console.log) 複製代碼
merge是將兩個數據流按時間軸順序合併起來,concat是把數據流連接到前面一個數據流後面(不管時間軸順序)
很顯而易見,輸出結果是0012314234, 123
7. Subject
在Rxjs中,有一個Subject類型,它具有Observer和Observable的功能,不僅可以使用操作符,還可以使用next、error、complete,但是本身不是操作符
// 看了前面的描述,那麼我們用的時候想產生數據源,很容易就會想到這樣的方法: let obs; Rx.Observable.create(observer => { obs = observer }).subscribe(console.log) obs.next(123) 複製代碼
但是,說好的函數式編程,不能有副作用,是純函數,因此需要subject了
const subject = new Rx.Subject() subject.map(x => x * 2).subscribe(console.log) subject.next(1) subject.next(2) subject.complete() 複製代碼
但是subject擅長於連接的特性,更重要的是用來做多播(一個對象被多個對象訂閱):
const source$ = Rx.Observable.interval(1000).take(3);// 從0開始每秒輸出一個數,輸出三個 source$.subscribe(x => {console.log('source1', x)}) setTimeout(() => { source$.subscribe(x => {console.log('source2', x)}) }, 1100); 複製代碼
那麼,問題來了,下面的輸出結果是:
const source$ = Rx.Observable.interval(1000).take(3);// 從0開始每秒輸出一個數,輸出三個 source$.subscribe(x => {console.log('source1', x)}) setTimeout(() => { source$.subscribe(x => {console.log('source2', x)}) }, 1100); 複製代碼
"source1先打印0,一秒後source1和2都打印1,再一秒後都打印3"
"恭喜答錯了。interval產生cold observable,數據源來自外部的纔是hot(幾個Fromxx的都是hot類型的),一對多的多播當然是要hot observable的,cold的訂閱一次就從新的Observable開始了。"
實際上答案應該是source1先打印0,後面兩秒source1和2分別打印10、21,最後source2打印2。那麼要實現上面那個理想的答案,應該用上subject。因爲有一個關鍵點,subject狀態唯一而統一,被自身實例subject.complete過後,再次subject.next也是無法被subscribe了。
我們利用一下subject就可以優雅而且不違反函數式編程規則來實現這個功能:
const source$ = new Rx.Subject(); let i = 0; const time = setInterval(() => { if (i === 2) { clearInterval(time) } source$.next(i ++) }, 1000) source$.subscribe(x => {console.log('s1', x)}) setTimeout(() => { source$.subscribe(x => {console.log('s2', x)}) }, 1100); 複製代碼
當然,我們還沒發揮Rxjs的api作用,我們還可以用multicast來連接subject實例
const source$ = Rx.Observable.interval(1000).take(3).multicast(new Rx.Subject()); source$.subscribe(x => {console.log('source1', x)}) setTimeout(() => { source$.subscribe(x => {console.log('source2', x)}) }, 1100); source$.connect() //需要手動調用, 不然前面代碼不會有結果 // 這纔是source1先打印0,一秒後source1和2都打印1,再一秒後都打印3的情況 複製代碼
總結
知識點:
- Observable.create(observer => {})是創建數據流基礎方法,裏面的observer有next、error方法吐出數據,complete方法表示完成整個過程(相當於empty操作符),當complete後,這個observer吐出的數據再也不能被下游subscribe到。每一次被subscribecreate裏面的函數都會調用一次
- hot Observable是隻訂閱subscribe後的數據,cold Observable訂閱這個Observable從頭到尾產生過的數據。這是因爲hot共享生產者,cold的是每一次subscribe都是一個新的生產者
- Subject具有Observable和observer的功能,所以我們就不用違反函數式編程的規則從外面拿到observer對象操作next了,可以直接用Subject的實例
- 看文檔,看各種操作符,如何鏈式調用,畫彈珠圖理解,你懂的
優點和特點
- Rxjs以Observable爲核心,全程通過發佈訂閱模式實現訂閱Observable的變化進行一系列操作
- 函數式+響應式編程,中間的操作符鏈式操作由next迭代器模式實現,並且由於是純函數所以每一次返回一 個新的Observable實例
- 在某些程度,可以單純拿出Observable一套當作像lodash、underscore這種工具庫使用
- Rxjs將所有的異步和同步數據流抽象成放在時間軸上處理的數據點,可以通過彈珠圖清晰理解整個數據流過程,處理異步的能力優秀
- 每一個數據流經過各種操作符操作,多個數據流協同、合併、連接,使得整個Rxjs應用在顯得流程清晰
缺點:
- api較多,學習成本高,比較抽象
- 代碼簡潔以鏈式操作爲主,維護性不如傳統的面向對象+模塊化
- 庫比較龐大,簡單問題需要引入一系列api使得項目文件體積變大,就算按需引入也比其他庫大