你有一份Rx編程祕籍請簽收

1、背景

在學習Rx編程的過程當中,理解Observable這個概念相當重要,常規學習過程當中,一般須要進行屢次「碰壁」才能逐漸「開悟」。這個有點像小時候學騎自行車,必須摔幾回才能掌握同樣。固然若是有辦法能「言傳」,則能夠少走一些彎路,儘快領悟Rx的精妙。java

2、Observable

Observable從字面翻譯來講叫作「可觀察者」,換言之就是某種「數據源」或者「事件源」,這種數據源具備可被觀察的能力,這個和你主動去撈數據有本質區別。用一個形象的比喻就是Observable比如是水龍頭,你能夠去打開水龍頭——訂閱Observable,而後水——數據就會源源不斷流出。這就是響應式編程的核心思想——變主動爲被動。不過這個不在本篇文章中詳解。編程

(圖片來源自網絡)緩存

Observable是一種概念,能夠經過不一樣的方式去具體實現,本文經過高階函數來實現兩個經常使用Observable:fromEvent和Interval。經過講解對Observable的訂閱和取消訂閱兩個行爲來幫助讀者真正理解Observable是什麼。網絡

3、高階函數

高階函數的概念來源於函數式編程,簡單的定義就是一個函數的入參或者返回值是一個函數的函數。例如:異步

function foo(arg){
    return function(){
        console.log(arg)
    }
}
const bar = foo(「hello world」)
bar()  // hello world
ps:高階函數能作的事情不少,這裏僅僅針對本文須要的情形進行使用。

上面這個foo函數的調用並不會直接打印hello world,而只是把這個hello world給緩存起來。後面咱們根據實際須要調用返回出來的bar函數,而後真正去執行打印hello world的工做。函數式編程

爲啥要作這麼一步封裝呢?實際上這麼作的效果就是「延遲」了調用。而一切的精髓就在這個「延遲」兩個字裏面。咱們其實是對一種行爲進行了包裝,看上去就像某種一致的東西,比如是快遞盒子。函數

(圖片來源自網絡)學習

裏面能夠裝不一樣的東西,但對於物流來講就是統一的東西。所以,就能夠造成對快遞盒的統一操做,好比堆疊、運輸、存儲、甚至是打開盒子這個動做也是一致的。spa

回到前面的例子,調用foo函數,至關於打包了一個快遞盒,這個快遞盒裏面有一個固定的程序,就是當打開這個快遞盒(調用bar)時執行一個打印操做。翻譯

咱們能夠有foo一、foo二、foo3……裏面有各類各樣的程序,可是這些foos,都有一個共同的操做就是「打開」。(前提是這個foo會返回一個函數,這樣才能知足「打開」的操做,即調用返回的函數)。

function foo1(arg){
    return function(){
       console.log(arg+"?")
    }
}
function foo2(arg){
      return function(){
         console.log(arg+"!")
     }
}
const bar1 = foo1(「hello world」)
const bar2 = foo2("yes")
bar1()+bar2() // hello world? yes!

4、快遞盒模型

4.1 快遞盒模型1:fromEvent

有了上面的基礎,下面咱們就來看一下Rx編程中最經常使用的一個Observable—fromEvent(……)。對於Rx編程的初學者,起初很難理解fromEvent(……)和addEventListener(……)有什麼區別。

btn.addEventListener("click",callback)
rx.fromEvent(btn,"click").subscribe(callback)

若是直接執行這個代碼,確實效果是同樣的。那麼區別在哪兒呢?最直接的區別是,subscribe函數做用在fromEvent(……)上而不是btn上,而addEventListener是直接做用在btn上的。subscribe函數是某種「打開」操做,而fromEvent(……)則是某種快遞盒。

fromEvent其實是對addEventListener的「延遲」調用

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
    }
}
const ob = fromEvent(btn,"click")
ob(console.log)// 至關於 subscribe

哦!fromEvent本質上是高階函數

至於如何實現subscribe來完成「打開」操做,不在本文討論範圍,在Rx編程中,這個subscribe的動做叫作「訂閱」。「訂閱」就是全部Observable的統一具有的操做。再次強調:本文中對Observable的「調用」在邏輯上至關於subscribe。

下面再舉一個例子,基本可讓讀者舉二反N了。

4.2 快遞盒模型2:interval

Rx中有一個interval,它和setInterval有什麼區別呢?

估計有人已經開始搶答了,interval就是對setInterval的延遲調用!bingo!

function interval(period){
    let i = 0
    return function(callback){
        setInterval(period,()=>callback(i++))
    }
}
const ob = interval(1000)
ob(console.log)// 至關於 subscribe

從上面兩個例子來看,不管是fromEvent(……)仍是Interval(……),雖然內部是徹底不一樣的邏輯,可是他們同屬於「快遞盒」這種東西,咱們把它稱之爲Observable——可觀察者

fromEvent和Interval自己只是製做「快遞盒」的模型,只有調用後返回的東西纔是「快遞盒」,即fromEvent(btn,"click")、interval(1000) 等等...

5、高階快遞盒

有了上面的基礎,下面開始進階:咱們擁有了那麼多快遞盒,那麼就能夠對這些快遞盒再封裝。

在文章開頭說了,快遞盒統一了一些操做,因此咱們能夠把許許多多的快遞盒堆疊在一塊兒,即組合成一個大的快遞盒!這個大的快遞盒和小的快遞盒同樣,具備「打開」操做(即訂閱)。當咱們打開這個大的快遞盒的時候,會發生什麼呢?

能夠有不少種不一樣的可能性,好比能夠逐個打開小的快遞盒(concat),或者一次性打開全部小的快遞盒(merge),也能夠只打開那個最容易打開的快遞盒(race)。

下面是一個簡化版的merge方法:

function merge(...obs){
    return function(callback){
        obs.forEach(ob=>ob(callback)) // 打開全部快遞盒
    }
}

咱們仍是拿以前的fromEvent和interval來舉例吧!

使用merge方法對兩個Observable進行組合:

const ob1 = fromEvent(btn,'click') // 製做快遞盒1
const ob2 = interval(1000) // 製做快遞盒2
const ob = merge(ob1,ob2) //製做大快遞盒
ob(console.log) // 打開大快遞盒

當咱們「打開」(訂閱)這個大快遞盒ob的時候,其中兩個小快遞盒也會被「打開」(訂閱),任意一個小快遞盒裏面的邏輯都會被執行,咱們就合併(merge)了兩個Observable,變成了一個。

這就是咱們爲何要辛辛苦苦把各類異步函數封裝成快遞盒(Observable)的緣由了——方便對他們進行統一操做!固然僅僅只是「打開」(訂閱)這個操做只是最初級的功能,下面開始進階。

6、銷燬快遞盒

6.1 銷燬快遞盒——取消訂閱

咱們仍是以fromEvent爲例子,以前咱們寫了一個簡單的高階函數,做爲對addEventListener的封裝:

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
    }
}

當咱們調用這個函數的時候,就生成了一個快遞盒(fromEvent(btn,'click'))。當咱們調用了這個函數返回的函數的時候,就是打開了快遞盒(fromEvent(btn,'click')(console.log))。

那麼咱們怎麼去銷燬這個打開的快遞盒呢?

首先咱們須要獲得一個已經打開的快遞盒,上面的函數調用結果是void,咱們沒法作任何操做,因此咱們須要構造出一個打開狀態的快遞盒。仍是使用高階函數的思想:在返回的函數裏面再返回一個函數,用於銷燬操做。

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
        return function(){
            target.removeEventListener(evtName,callback)
        }
    }
}
const ob = fromEvent(btn,'click') // 製做快遞盒
const sub = ob(console.log) // 打開快遞盒,並獲得一個可用於銷燬的函數
sub() // 銷燬快遞盒

同理,對於interval,咱們也能夠如法炮製:

function interval(period){
    let i = 0
    return function(callback){
        let id = setInterval(period,()=>callback(i++))
        return function(){
            clearInterval(id)
        }
    }
}
const ob = interval(1000) // 製做快遞盒
const sub = ob(console.log) // 打開快遞盒
sub() // 銷燬快遞盒

6.2 銷燬高階快遞盒

咱們以merge爲例:

function merge(...obs){
    return function(callback){
        const subs = obs.map(ob=>ob(callback)) // 訂閱全部並收集全部的銷燬函數
        return function(){
            subs.forEach(sub=>sub()) // 遍歷銷燬函數並執行
        }
    }
}
 
const ob1 = fromEvent(btn,'click') // 製做快遞盒1
const ob2 = interval(1000) // 製做快遞盒2
const ob = merge(ob1,ob2) //製做大快遞盒
const sub = ob(console.log) // 打開大快遞盒
sub() // 銷燬大快遞盒

當咱們銷燬大快遞盒的時候,就會把裏面全部的小快遞盒一塊兒銷燬。

6、補充

到這裏咱們已經將Observable的兩個重要操做(訂閱、取消訂閱)講完了,值得注意的是,取消訂閱這個行爲並不是是做用於Observable上,而是做用於已經「打開」的快遞盒(訂閱Observable後返回的東西)之上!

Observable除此之外,還有兩個重要操做,即發出事件、完成/異常,(這兩個操做屬因而由Observable主動發起的回調,和操做的方向是相反的,因此其實不能稱之爲操做)。

這個兩個行爲用快遞盒就不那麼形象了,咱們能夠將Observable比作是水龍頭,原先的打開快遞盒變成擰開水龍頭,而咱們傳入的回調函數就能夠比喻成接水的水杯!因爲你們對回調函數已經很是熟悉了,因此本文就再也不贅述了。

7、後記

總結一下咱們學習的內容,咱們經過高階函數將一些操做進行了「延遲」,並賦予了統一的行爲,好比「訂閱」就是延遲執行了異步函數,「取消訂閱」就是在上面的基礎上再「延遲」執行了銷燬資源的函數。

這些所謂的「延遲」執行就是Rx編程中幕後最難理解,也是最核心的部分。Rx的本質就是將異步函數封裝起來,而後抽象成四大行爲:訂閱、取消訂閱、發出事件、完成/異常。

實際實現Rx庫的方法有不少,本文只是利用了高階函數的思想來幫助你們理解Observable的本質,在官方實現的版本中,Observable這個快遞盒並不是是高階函數,而是一個對象,但本質上是同樣的,這裏引出了一個話題:函數式編程與面向對象的異同,請聽下回分解。

做者:vivo互聯網開發團隊-Li Yuxiang
相關文章
相關標籤/搜索