淺談Rx響應式程式設計
目錄
- 一、Observable
- 二、高階函式
- 三、快遞盒模型
- 3.1、快遞盒模型1:fromEvent
- 3.2、快遞盒模型2:interval
- 四、高階快遞盒
- 五、銷燬快遞盒
- 5.1、銷燬快遞盒——取消訂閱
- 5.2、銷燬高階快遞盒
- 六、補充
- 七、後記
一、Observable
Observable從字面翻譯來說叫做“可觀察者”,換言之就是某種“資料來源”或者“事件源”,這種資料來源具有可被觀察的能力,這個和你主動去撈資料有本質區別。用一個形象的比喻就是Observable好比是水龍頭,你可以去開啟水龍頭——訂閱Observable,然後水——資料就會源源不斷流出。這就是響應式程式設計的核心思想——變主動為被動。不過這個不在本篇文章中詳解。
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的工作。
為啥要做這麼一步封裝呢?實際上這麼做的效果就是“延遲”了呼叫。而一切的精髓就在這個“延遲”兩個字裡面。我們實際上是對一種行為進行了包裝,看上去就像某種一致的東西,好比是快遞盒子。
裡面可以裝不同的東西,但對於物流來說就是統一的東西。因此,就可以形成對快遞盒的統一操作,比如堆疊、運輸、儲存、甚至是開啟盒子這個動作也是一致的。
回到前面的例子,呼叫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) 等等...
四、高階快遞盒
有了上面的基礎,下面開始進階:我們擁有了那麼多快遞盒,那麼就可以對這些快遞盒再封裝。
在文章開頭說了,快遞盒統一了一些操作,所以我們可以把許許多多的快遞盒堆疊在一起,即組合成一個大的快遞盒!這個大的快遞盒和小的快遞盒一樣,具有“開啟”操作(即訂閱)。當我們開啟這個大的快遞盒的時候,會發生什麼呢?
可以有很多種不同的可能性,比如可以逐個開啟小的快遞盒(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)的原因了——方便對他們進行統一操作!當然僅僅只是“開啟”(訂閱)這個操作只是最初級的功能,下面開始進階。
五、銷燬快遞盒
5.1、銷燬快遞盒——取消訂閱
我們還是以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、銷燬高階快遞盒
我們以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響應式程式設計的資料請關注我們其它相關文章!