前幾天我們講完了能把 Higher Order Observable 轉(zhuǎn)成一般的 Observable 的 operators,今天我們要講能夠把一般的 Observable 轉(zhuǎn)成 Higher Order Observable 的 operators。
其實(shí)前端不太有機(jī)會(huì)用到這類型的 Operators,都是在比較特殊的需求下才會(huì)看到,但還是會(huì)有遇到的時(shí)候。
Operators
window
window 是一整個(gè)家族,總共有五個(gè)相關(guān)的 operators
- window
- windowCount
- windowTime
- windowToggle
- windowWhen
這裡我們只介紹 window 跟 windowToggle 這兩個(gè)方法,其他三個(gè)的用法相對(duì)都簡(jiǎn)單很多,大家如果有需要可以再自行到官網(wǎng)查看。
window 很類似 buffer 可以把一段時(shí)間內(nèi)送出的元素拆出來,只是 buffer 是把元素拆分到陣列中變成
Observable<T> => Observable<Array<T>>
而 window 則是會(huì)把元素拆分出來放到新的 observable 變成
Observable<T> => Observable<Observable<T>>
buffer 是把拆分出來的元素放到陣列并送出陣列;window 是把拆分出來的元素放到 observable 并送出 observable,讓我們來看一個(gè)例子
var click = Rx.Observable.fromEvent(document, 'click');
var source = Rx.Observable.interval(1000);
var example = source.window(click);
example
.switch()
.subscribe(console.log);
// 0
// 1
// 2
// 3
// 4
// 5 ...
首先 window 要傳入一個(gè) observable,每當(dāng)這個(gè) observable 送出元素時(shí),就會(huì)把正在處理的 observable 所送出的元素放到新的 observable 中并送出,這裡看 Marble Diagram 會(huì)比較好解釋
click : -----------c----------c------------c--
source : ----0----1----2----3----4----5----6---..
window(click)
example: o----------o----------o------------o--
\ \ \
---0----1-|--2----3--|-4----5----6|
switch()
: ----0----1----2----3----4----5----6---...
這裡可以看到 example 變成發(fā)送 observable 會(huì)在每次 click 事件發(fā)送出來后結(jié)束,并繼續(xù)下一個(gè) observable,這裡我們用 switch 才把它攤平。
當(dāng)然這個(gè)范例只是想單存的表達(dá) window 的作用,沒什麼太大的意義,實(shí)務(wù)上 window 會(huì)搭配其他的 operators 使用,例如我們想計(jì)算一秒鐘內(nèi)觸發(fā)了幾次 click 事件
var click = Rx.Observable.fromEvent(document, 'click');
var source = Rx.Observable.interval(1000);
var example = click.window(source)
example
.map(innerObservable => innerObservable.count())
.switch()
.subscribe(console.log);
注意這裡我們把 source 跟 click 對(duì)調(diào)了,并用到了 observable 的一個(gè)方法 count(),可以用來取得 observable 總共送出了幾個(gè)元素,用 Marble Diagram 表示如下
source : ---------0---------1---------2--...
click : --cc---cc----c-c----------------...
window(source)
example: o--------o---------o---------o--..
\ \ \ \
-cc---cc|---c-c---|---------|--..
count()
: o--------o---------o---------o--
\ \ \ \
-------4|--------2|--------0|--..
switch()
: ---------4---------2---------0--...
從 Marble Diagram 中可以看出來,我們把部分元素放到新的 observable 中,就可以利用 Observable 的方法做更靈活的操作
windowToggle
windowToggle 不像 window 只能控制內(nèi)部 observable 的結(jié)束,windowToggle 可以傳入兩個(gè)參數(shù),第一個(gè)是開始的 observable,第二個(gè)是一個(gè) callback 可以回傳一個(gè)結(jié)束的 observable,讓我們來看范例
var source = Rx.Observable.interval(1000);
var mouseDown = Rx.Observable.fromEvent(document, 'mousedown');
var mouseUp = Rx.Observable.fromEvent(document, 'mouseup');
var example = source
.windowToggle(mouseDown, () => mouseUp)
.switch();
example.subscribe(console.log);
一樣用 Marble Diagram 會(huì)比較好解釋
source : ----0----1----2----3----4----5--...
mouseDown: -------D------------------------...
mouseUp : ---------------------------U----...
windowToggle(mouseDown, () => mouseUp)
: -------o-------------------------...
\
-1----2----3----4--|
switch()
example : ---------1----2----3----4---------...
從 Marble Diagram 可以看得出來,我們用 windowToggle 拆分出來內(nèi)部的 observable 始于 mouseDown 終于 mouseUp。
groupBy
最后我們來講一個(gè)實(shí)務(wù)上比較常用的 operators - groupBy,它可以幫我們把相同條件的元素拆分成一個(gè) Observable,其實(shí)就跟平常在下 SQL 是一樣個(gè)概念,我們先來看個(gè)簡(jiǎn)單的例子
var source = Rx.Observable.interval(300).take(5);
var example = source
.groupBy(x => x % 2);
example.subscribe(console.log);
// GroupObservable { key: 0, ...}
// GroupObservable { key: 1, ...}
上面的例子,我們傳入了一個(gè) callback function 并回傳 groupBy 的條件,就能區(qū)分每個(gè)元素到不同的 Observable 中,用 Marble Diagram 表示如下
source : ---0---1---2---3---4|
groupBy(x => x % 2)
example: ---o---o------------|
\ \
\ 1-------3----|
0-------2-------4|
在實(shí)務(wù)上,我們可以拿 groupBy 做完元素的區(qū)分后,再對(duì) inner Observable 操作,例如下面這個(gè)例子我們將每個(gè)人的分?jǐn)?shù)作加總再送出
var people = [
{name: 'Anna', score: 100, subject: 'English'},
{name: 'Anna', score: 90, subject: 'Math'},
{name: 'Anna', score: 96, subject: 'Chinese' },
{name: 'Jerry', score: 80, subject: 'English'},
{name: 'Jerry', score: 100, subject: 'Math'},
{name: 'Jerry', score: 90, subject: 'Chinese' },
];
var source = Rx.Observable.from(people)
.zip(
Rx.Observable.interval(300),
(x, y) => x);
var example = source
.groupBy(person => person.name)
.map(group => group.reduce((acc, curr) => ({
name: curr.name,
score: curr.score + acc.score
})))
.mergeAll();
example.subscribe(console.log);
// { name: "Anna", score: 286 }
// { name: 'Jerry', score: 270 }
這裡我們范例是想把 Jerry 跟 Anna 的分?jǐn)?shù)個(gè)別作加總,畫成 Marble Diagram 如下
source : --o--o--o--o--o--o|
groupBy(person => person.name)
: --i--------i------|
\ \
\ o--o--o|
o--o--o--|
map(group => group.reduce(...))
: --i---------i------|
\ \
o| o|
mergeAll()
example: --o---------o------|
今日小結(jié)
今天講了兩個(gè)可以把元素拆分到新的 observable 的 operators,這兩個(gè) operators 在前端比較少用到,但在后端或是比較複雜了前端應(yīng)用才比較有機(jī)會(huì)用到。不知道讀者有沒有收穫呢? 如果有任何問題歡迎留言給我,謝謝。