30 天精通 RxJS(15): Observable Operators - distinct, distinctUntilChanged

新的一年立刻就要到了,各位讀者都去哪裏跨年呢? 筆者很可憐的只能一邊寫文章一邊跨年,今天就簡單看幾個 operators 讓你們好好跨年吧!javascript

昨天咱們講到了 throttle 跟 debounce 兩個方法來作性能優化,其實還有另外一個方法能夠作性能的優化處理,那就是 distinct。java

Operators

distinct

若是會下 SQL 指令的應該都對 distinct 不陌生,它能幫咱們把相同值的資料濾掉只留一筆,RxJS 裏的 distinct 也是相同的做用,讓咱們直接來看示例性能優化

var source = Rx.Observable.from(['a', 'b', 'c', 'a', 'b'])
            .zip(Rx.Observable.interval(300), (x, y) => x);
var example = source.distinct()

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
// a
// b
// c
// complete複製代碼

JSBin | JSFiddlebash

若是用 Marble Diagram 表示以下性能

source : --a--b--c--a--b|
            distinct()
example: --a--b--c------|複製代碼

從上面的示例能夠看得出來,當咱們用 distinct 後,只要有重複出現的值就會被過濾掉。優化

另外咱們能夠傳入一個 selector callback function,這個 callback function 會傳入一個接收到的元素,並回傳咱們真正但願比對的值,舉例以下ui

var source = Rx.Observable.from([{ value: 'a'}, { value: 'b' }, { value: 'c' }, { value: 'a' }, { value: 'c' }])
            .zip(Rx.Observable.interval(300), (x, y) => x);
var example = source.distinct((x) => {
    return x.value
});

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
// {value: "a"}
// {value: "b"}
// {value: "c"}
// complete複製代碼

JSBin | JSFiddlespa

這裏能夠看到,由於 source 送出的都是實例,而 js 事件的比對是比對內存位置,因此在這個例子中這些實例永遠不會相等,但實際上咱們想比對的是實例中的 value,這時咱們就能夠傳入 selector callback,來選擇咱們要比對的值。.net

distinct 傳入的 callback 在 RxJS 5 幾個 bate 版本中有過不少改變,如今網路上不少文章跟教學都是過期的,請讀者務必當心!code

實際上 distinct() 會在背地裏創建一個 Set,當接收到元素時會先去判斷 Set 內是否有相同的值,若是有就不送出,若是沒有則存到 Set 並送出。因此記得儘可能不要直接把 distinct 用在一個無限的 observable 裏,這樣極可能會讓 Set 愈來愈大,建議你們能夠放第二個參數 flushes,或用 distinctUntilChanged

這裏指的 Set 實際上是 RxJS 本身實現的,跟 ES6 原生的 Set 行爲也都一致,只是由於 ES6 的 Set 支持程度還並不理想,因此這裏是直接用 JS 實現。

distinct 能夠傳入第二個參數 flushes observable 用來清除暫存的資料,示例以下

var source = Rx.Observable.from(['a', 'b', 'c', 'a', 'c'])
            .zip(Rx.Observable.interval(300), (x, y) => x);
var flushes = Rx.Observable.interval(1300);
var example = source.distinct(null, flushes);

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
// a
// b
// c
// c
// complete複製代碼

JSBin | JSFiddle

這裏咱們用 Marble Diagram 比較好表示

source : --a--b--c--a--c|
flushes: ------------0---...
        distinct(null, flushes);
example: --a--b--c-----c|複製代碼

其實 flushes observable 就是在送出元素時,會把 distinct 的暫存清空,因此以後的暫存就會從頭來過,這樣就不用擔憂暫存的 Set 越來愈大的問題,但其實咱們日常不太會用這樣的方式來處理,一般會用另外一個方法 distinctUntilChanged。

distinctUntilChanged

distinctUntilChanged 跟 distinct 同樣會把相同的元素過濾掉,但 distinctUntilChanged 只會跟最後一次送出的元素比較,不會每一個都比,舉例以下

var source = Rx.Observable.from(['a', 'b', 'c', 'c', 'b'])
            .zip(Rx.Observable.interval(300), (x, y) => x);
var example = source.distinctUntilChanged()

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
// a
// b
// c
// b
// complete複製代碼

JSBin | JSFiddle

這裏 distinctUntilChanged 只會暫存一個元素,並在收到元素時跟暫存的元素比對,若是同樣就不送出,若是不同就把暫存的元素換成剛接收到的新元素並送出。

source : --a--b--c--c--b|
            distinctUntilChanged()
example: --a--b--c-----b|複製代碼

從 Marble Diagram 中能夠看到,第二個 c 送出時恰好上一個就是 c 因此就被濾掉了,但最後一個 b 則跟上一個不一樣因此沒被濾掉。

distinctUntilChanged 是比較常在開發中上使用的,最多見的情況是咱們在作多方同步時。當咱們有多個 Client,且每一個 Client 有着各自的狀態,Server 會再一個 Client 須要變更時通知全部 Client 更新,但可能某些 Client 接收到新的狀態其實跟上一次收到的是相同的,這時咱們就可用 distinctUntilChanged 方法只處理跟最後一次不相同的訊息,像是多方通話、多裝置的資訊同步都會有相似的情境。

今日小結

今天講了兩個 distinct 方法,這兩個方法日常可能用不太到,但在需求複雜的應用裏是不可或缺的好方法,尤爲要處理很是多人即時同步的情境下,這會是很是好用的方法,不知道讀者們今天有沒有收穫呢? 若是有任何問題,歡迎在下方留言給我,感謝!

相關文章
相關標籤/搜索