[JavaScript] 你所不知道的響應(yīng)式編程

這幾年Facebook的React火了,它涉及到了很多出乎意料的JSX,思維奇特的Redux。
其中,響應(yīng)式編程(Reactive Programming)和數(shù)據(jù)流的思想在React背后起到了推動作用。

什么?響應(yīng)式編程?
我會呀,不就是讓網(wǎng)頁響應(yīng)不同尺寸的設(shè)備嗎?原理是CSS3的媒體查詢。《響應(yīng)式Web設(shè)計(jì)》

非也非也。。
維基百科上對響應(yīng)式編程是這樣解釋的,

In computing, reactive programming is a programming paradigm oriented around data flows and the propagation of change.

響應(yīng)式編程是面向數(shù)據(jù)流和變化傳播的一種編程范型。
它與其他編程范型混合后,產(chǎn)生了,面向?qū)ο蟮捻憫?yīng)式編程(OORP),函數(shù)響應(yīng)式編程(FRP)等一些"新"概念。


1. ReactiveX

本文主要介紹ReactiveX,它是響應(yīng)式編程的一種實(shí)現(xiàn)。簡稱Rx,是Reactive Extensions的縮寫。
最初,它是LINQ的一個(gè)擴(kuò)展,由微軟的架構(gòu)師Erik Meijer領(lǐng)導(dǎo)的團(tuán)隊(duì)開發(fā),在2012年11月開源。
現(xiàn)在它已經(jīng)擴(kuò)展到了很多種語言中,例如Java,JS,.NET,Scala,Clojure,Swift等等。

說起響應(yīng)式編程,其實(shí)我們遇到過。
在Excel中,我們讓B1=A1/5,那么一旦我們修改了A1單元格的值,B1就會隨著改變。
即,B1響應(yīng)了A1的變化,這就是響應(yīng)式。

在Web應(yīng)用中,這樣的場景更多,UI上的每個(gè)事件,都會造成一系列改變。
我們可以通過注冊回調(diào)函數(shù)來解決這個(gè)問題,
可是,異步編程控制起來是一個(gè)難題,例如callback hell,
于是,ES6加入了Promise,ES7提案了Await/Async等解決方案。

而Rx采用了另外的方式,它結(jié)合了觀察者模式和迭代器模式,用了一種稱為Observable的數(shù)據(jù)結(jié)構(gòu)。
Observable是一層抽象,封裝了同步數(shù)據(jù)和異步數(shù)據(jù),
通過Observable之間進(jìn)行變換,將最終結(jié)果反映到觀察者那里。
它已經(jīng)作為ES7的提案。

What's the difference between an array and events? — Erik Meijer

Observable統(tǒng)一了列表和事件。


2. 列表上的高階函數(shù)

JS支持高階函數(shù),指的是JS的函數(shù)可以接受其他函數(shù)作為參數(shù),或者可以把另一個(gè)函數(shù)作為返回值。
這給了JS很強(qiáng)的表達(dá)能力和靈活性。

JS的Array類型,有很多好用的高階函數(shù),例如,map,filter,reduce...
我們先看看它們的用法吧。

[1,2,3].map((x)=>2*x);    //[2,4,6]
[1,2,3].filter((x)=>x%2!=0);    //[1,3]
[1,2,3].reduce((v,m)=>m+v,0);    //6

Underscore.js實(shí)現(xiàn)了很多其他的高階函數(shù),例如,take,reduce,pluck等等,
ES5規(guī)范也在一定程度上受到了它的影響。

例如:

_.take([1,2,3],2);    //[1,2]

高階函數(shù),讓我們避免了使用列表的索引。
這也使得它們可以用于其他集合類型中。


3. Observable

Observable就是這樣的一種新型集合類型。
它包含一系列的從被觀察對象推送給觀察者的值。

var button=document.getElementById('button1');
var clicks=Rx.Observable.fromEvent(button,'click');

clicks.take(1)
    .subscribe((e)=>console.log('clicked'));

Rx把按鈕未來的所有點(diǎn)擊事件看成了一個(gè)無窮長的序列,即Observable,
因?yàn)椴簧婕八饕?,高階函數(shù)就可以處理這個(gè)序列了。
以上代碼,輕松實(shí)現(xiàn)了只觸發(fā)一次的點(diǎn)擊事件。

其中take,表示取該序列的第一項(xiàng)構(gòu)成一個(gè)新的序列,
subscribe表示連接Observable與Observer。


4. Operator

Rx提供了非常多的處理Observable的函數(shù),稱為Operators。

例如:
range(n,len)
從n開始,len個(gè)數(shù)字
Rx.Observable.range(2,2)~[2,3]

concatMap(fn)
把Observable中的每個(gè)item替換成多個(gè),并保持順序

fromArray(arr)
把數(shù)組轉(zhuǎn)為Observable

fromEvent(dom,name)
把dom元素的事件流轉(zhuǎn)為Observable

pluck(name)
用item的名為name的屬性值組成新的Observable
Rx.Observable.fromArray({value:0},{value:1},{v:2}).pluck('value')~[0,1,undefined]

debounce(time)
過濾掉那些間隔小于time ms的item

merge(obs)
合并兩個(gè)Observable

filter(predicator)
取出滿足限制條件的item

of(item1,item2,...)
將參數(shù)列表轉(zhuǎn)換成Observable

interval(time)
創(chuàng)建一個(gè)Observable,它每隔time ms發(fā)送一個(gè)數(shù)字


5. Observer

Observable可以通過Operator來轉(zhuǎn)換,但最終要由觀察者來訂閱。
Observer由下面的方法來創(chuàng)建,

var observer=Rx.Observer.create(onNext,onError,onCompleted);
observable.subscribe(observer);

其中,onError,onCompleted可以省略,
onNext在每推送一項(xiàng)到序列中時(shí)觸發(fā),
onError在某個(gè)環(huán)節(jié)中throw出一個(gè)值時(shí)觸發(fā),
onCompleted在整個(gè)Observable序列結(jié)束時(shí)觸發(fā)。

這樣observable的改變就能通知給observer了。


6. 手動創(chuàng)建Observable

前文提到的Observable可以由列表或DOM事件得到(fromArray/fromEvent),
Observable類型的對象也可以直接創(chuàng)建。

var observable=Rx.Observable.create((observer)=>{
    observer.onNext(1);
    observer.onNext(2);
    observer.onCompleted();
});

通過主動對observer的調(diào)用,observer.onNext可以用于各種同步和異步的場景中。

因?yàn)榇蟛糠植僮鱎x都提供了Operator,所以,直接創(chuàng)建Observable多用于底層的一些實(shí)現(xiàn)中。
例如,Rx.DOM對象中提供了請求JSONP并轉(zhuǎn)換成Observable的功能。

var data=Rx.DOM.jsonpRequest({
    url:DATA_URL
});

結(jié)語

響應(yīng)式編程內(nèi)容很多,改變了我們的看待事件的方式,
我們先搭建起數(shù)據(jù)流的管道,然后等待數(shù)據(jù)沿著管道被數(shù)據(jù)源推送給觀察者。
這種思想統(tǒng)一了同步和異步,是比Promise更強(qiáng)大的處理方案。

熟悉LINQ的同學(xué)可能有所體會,Rx好像LINQ的Method Syntax,
把事件源看成了數(shù)據(jù)庫進(jìn)行查詢LINQ to "Events",
用一系列Lambda表達(dá)式串聯(lián)得到最終結(jié)果。

學(xué)習(xí)響應(yīng)式編程,對學(xué)習(xí)React有很大幫助,
也只有在數(shù)據(jù)流方面處理得當(dāng),才能避免意外增加View層的復(fù)雜度,
我想,這才是Flux,Redux出現(xiàn)的根本原因吧。


參考:
Reactive programming
ReactiveX
Functional Programming in Javascript
Reactive Programming with RxJS
Query Syntax and Method Syntax in LINQ
es-observable

擴(kuò)展閱讀:
Cycle.js
Functional reactive programming
Your Mouse is a Database
Facebook just taught us all how to build websites
Functional Reactive React.js

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容