1. 程式人生 > 程式設計 >淺談Rx響應式程式設計

淺談Rx響應式程式設計

目錄
  • 一、Observable
  • 二、高階函式
  • 三、快遞盒模型
    • 3.1、快遞盒模型1:fromEvent
    • 3.2、快遞盒模型2:interval
  • 四、高階快遞盒
    • 五、銷燬快遞盒
      • 5.1、銷燬快遞盒——取消訂閱
      • 5.2、銷燬高階快遞盒
    • 六、補充
      • 七、後記

        一、Observable

        Observable從字面翻譯來說叫做“可觀察者”,換言之就是某種“資料來源”或者“事件源”,這種資料來源具有可被觀察的能力,這個和你主動去撈資料有本質區別。用一個形象的比喻就是Observable好比是水龍頭,你可以去開啟水龍頭——訂閱Observable,然後水——資料就會源源不斷流出。這就是響應式程式設計的核心思想——變主動為被動。不過這個不在本篇文章中詳解。

        淺談Rx響應式程式設計

        Observable是一種概念,可以通過不同的方式去具體實現,本文通過高階函式來實現兩個常用Observable:fromEvent和Interval。通過講解對Observable的訂閱和取消訂閱兩個行為來幫助讀者真正理解Observable是什麼。

        二、高階函式

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

        funhttp://www.cppcns.comction foo(arg){
            return function(){
                console.log(arg)
            }
        }
        const bar = foo(“hello world”)
        bar()  // hello world

        ps:高階函式能做的事情很多,這裡僅僅針對本文需要的情形進行使用。

        上面這個foo函式的呼叫並不會直接列印hello world,而只是把這個hello world給快取起來。後面我們根據實際需要呼叫返回出來的bar函式,然後真正去執行列印hello world的工作。

        為啥要做這麼一步封裝呢?實際上這麼做的效果就是“延遲”了呼叫。而一切的精髓就在這個“延遲”兩個字裡面。我們實際上是對一種行為進行了包裝,看上去就像某種一致的東西,好比是快遞盒子。

        淺談Rx響應式程式設計

        裡面可以裝不同的東西,但對於物流來說就是統一的東西。因此,就可以形成對快遞盒的統一操作,比如堆疊、運輸、儲存、甚至是開啟盒子這個動作也是一致的。

        回到前面的例子,呼叫foo函式,相當於打包了一個快遞盒,這個快遞盒裡面有一個固定的程式,就是當開啟這個快遞盒(呼叫bar)時執行一個列印操作。

        我們可以有foo1、foo2、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!

        三、快遞盒模型

        3.1、快遞盒模型1:fromEvent

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

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

        如果直接執行這個程式碼,確實效果是一樣的。那麼區別在哪兒呢?最直接的區別是,subscribe函式作用在fromEvent(……)上而不是btn上,而addEventListener是直接作用在btn上的。subscribehttp://www.cppcns.com函式是某種“開啟”操作,而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了。

        3.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本身只是製作“快遞盒”的模型,只有呼叫後返回的東西才是“快遞盒”,即frowww.cppcns.commEvent(btn,"click")、interval(1000) 等等...

        四、高階快遞盒

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

        淺談Rx響應式程式設計

        在文章開頭說了,快遞盒統一了一些操作,所以我們可以把許許多多的快遞盒堆疊在一起,即組合成一個大的快遞盒!這個大的快遞盒和小的快遞盒一樣,具有“開啟”操作(即訂閱)。當我們開啟這個大的快遞盒的時候,會發生什麼呢?

        可以有很多種不同的可能性,比如可以逐個開啟小的快遞盒(concat),或者一次性開啟所有小的快遞盒(merge),也可以只打開那個最容易開啟的快遞盒(race)。

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

        function merge(...obs){
            return function(callback){
                obs.forEach(ob=>ob(callback)) // 開啟所有快遞盒
            }
        }

        我們還是拿之前的fromEvent和interval來舉例吧!

        淺談Rx響應式程式設計

        使用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)的原因了——方便對他們進行統一操作!當然僅僅只是“開啟”(訂閱)這個操作只是最初級的功能,下面開始進階。

        五、銷燬快遞盒

        5.1、銷燬快遞盒——取消訂閱

        淺談Rx響應式程式設計

        我們還是以fromEvent為例子,之前我們寫了一個簡單的高階函式,作為對addEventListener的封裝:

        function fromEvent(target,callback)
            }
        }
        http://www.cppcns.com

        當我們呼叫這個函式的時候,就生成了一個快遞盒(fromEvent(btn,'程式設計客棧click'))。當我們呼叫了這個函式返回的函式的時候,就是打開了快遞盒(fromEvent(btn,'click')(console.log))。

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

        首先我們需要得到一個已經開啟的快遞盒,上面的函式呼叫結果是void,我們無法做任何操作,所以我們需要構造出一個開啟狀態的快遞盒。還是使用高階函式的思想:在返回的函式裡面再返回一個函式,用於銷燬操作。

        function fromEvent(target,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() // 銷燬快遞盒

        5.2、銷燬高階快遞盒

        淺談Rx響應式程式設計

        我們以merge為例:

        function merge(...obs){
            return function(callback){
                const subs = obs.map(ob=>ob(callback)) // 訂閱所有並收集所有的銷燬函式
                return function(){
                    subs.forEach(sub=>sub()) // 遍歷銷燬函式並執行
                }
            }
        }
         
        const ob1 = fromEvent(btn,ob2) //製作大快遞盒
        const sub = ob(console.log) // 開啟大快遞盒
        sub() // 銷燬大快遞盒

        當我們銷燬大快遞盒的時候,就會把裡面所有的小快遞盒一起銷燬。

        六、補充

        到這裡我們已經將Observable的兩個重要操作(訂閱、取消訂閱)講完了,值得注意的是,取消訂閱這個行為並非是作用於Observable上,而是作用於已經“開啟”的快遞盒(訂閱Observable後返回的東西)之上!

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

        這個兩個行為用快遞盒就不那麼形象了,我們可以將Observable比做是水龍頭,原先的開啟快遞盒變成擰開水龍頭,而我們傳入的回撥函式就可以比喻成接水的水杯!由於大家對回撥函式已經非常熟悉了,所以本文就不再贅述了。

        七、後記

        總結一下我們學習的內容,我們通過高階函式將一些操作進行了“延遲”,並賦予了統一的行為,比如“訂閱”就是延遲執行了非同步函式,“取消訂閱”就是在上面的基礎上再“延遲”執行了銷燬資源的函式。

        這些所謂的“延遲”執行就是Rx程式設計中幕後最難理解,也是最核心的部分。Rx的本質就是將非同步函式封裝起來,然後抽象成四大行為:訂閱、取消訂閱、發出事件、完成/異常。

        實際實現Rx庫的方法有很多,本文只是利用了高階函式的思想來幫助大家理解Observable的本質,在官方實現的版本中,Observable這個快遞盒並非是高階函式,而是一個物件,但本質上是一樣的

        以上就是淺談Rx響應式程式設計的詳細內容,更多關於Rx響應式程式設計的資料請關注我們其它相關文章!