摘錄于:https://mp.weixin.qq.com/s/illfwfPNbpf62zZcOxEBDQ
Reactive Extension for JavaScript
有各種語言的實現(xiàn),如 RxJava,RxPy,RxGo 等等...,其中的 Rx 是指 Reactive Extension,指響應(yīng)式編程(Reactive Programming)這種編程范式在 JavaScript 的一種實現(xiàn)。
什么是 Reactive Programming?
響應(yīng)式編程(Reactive Programming)是融合了觀察者模式、迭代器模式與函數(shù)式編程三者最優(yōu)秀的觀點,從而誕生的一種新的編程范式,是一種以異步數(shù)據(jù)流(Async Data Stream)為中心的編程范式。
關(guān)于流 Stream 我們會在后續(xù)進(jìn)行講解。
這里可能會引出如下幾個疑問?
- 什么是觀察者模式、迭代器模式?
- 什么是主動式(Proactive),什么是響應(yīng)式(Reactive)?
- 什么是命令式(Imperactive)、什么是函數(shù)式(Functional/Declaractive)、什么是響應(yīng)式(Reactive)?
觀察者模式與迭代器模式
觀察者模式定了一個對象之間的一對多的依賴關(guān)系,當(dāng)目標(biāo)對象 Subject 更新時,所有依賴此 Subject 的 Observer 都會收到更新。
舉個例子??
import { fromEvent } from "rxjs";
// 創(chuàng)建一個監(jiān)聽 document click 事件的 Observable
let Observable = fromEvent(document, "click");
// 通過 Observable.subscribe 時,接收一個 Observer 回調(diào),當(dāng)有點擊事件(click)發(fā)生時
// 則調(diào)用傳入的回調(diào)函數(shù),即 Observer 會收到更新
let subscription = Observable.subscribe((e) => {
console.log("dom clicked");
});
let subscription2 = Observable.subscribe((e) => {
console.log("dom clicked");
});
let subscription3 = Observable.subscribe((e) => {
console.log("dom clicked");
});
上述代碼,當(dāng)點擊 DOM 時,三個 observer (回調(diào)函數(shù))都會收到通知,然后打印 dom clicked 語句。
迭代器模式是指提供一種方法順序訪問一個聚合對象中各個元素,而不需要暴露該對象的具體表示,常見的為部署 Symbol.iterator 屬性,調(diào)用對應(yīng) Symbol.iterator 的方法返回一個迭代器對象,然后就可以以統(tǒng)一的方式進(jìn)行遍歷:
let arr = ['a', 'b', 'c'];
let iterator = arr[Symbol.iterator]( "Symbol.iterator")
iterator.next(); // { value: 'a', done: false }
iterator.next(); // { value: 'b', done: false }
iterator.next(); // { value: 'c', done: false }
iterator.next(); // { value: undefined, done: true }
對應(yīng)的 RxJS 里面就是 Observable 可觀察對象,也就是我們后續(xù)將引出的 Stream 流的概念,每個 Stream/Observable 其實可以看作是一個數(shù)組,然后支持?jǐn)?shù)組相關(guān)的各種操作、變換等,變成另外一個 Stream/Observable,拿 RxJS 舉例:
import { fromEvent, map } from "rxjs";
// 創(chuàng)建一個監(jiān)聽 document click 事件的 Observable
let subscription = fromEvent(document, "click")
.pipe(map(e => e.target)
.subscribe(value => {
console.log('click: ', value);
});
fromEvent(document, "click") 會聲明一個 Observable 對象,同時也創(chuàng)建了一個 Stream,類似下面的圖片:

fromEvent(document, "click") 創(chuàng)建的 Observable 對應(yīng)著上面的帶有箭頭的線,這條線就是一個 Stream 流,上面的一個個 ev 就是每次點擊之后產(chǎn)生的事件,隨著時間推移,不斷的產(chǎn)生事件,在這個線上不斷的流動下去 -- 之所以為 Stream,而這個 Stream 其實也可以看作是一個 “數(shù)組”,上面的一個個事件即為 “數(shù)組” 的元素,我們可以對這個 “數(shù)組”進(jìn)行遍歷,以統(tǒng)一的方式如 map/filter 等進(jìn)行遍歷,所以也叫融合了迭代器模式,而在 RxJS 中,通過這種 “迭代器” 模式,我們可以方便的對一個 Stream 進(jìn)行變換,如 map 操作效果如下圖所示:

map 將一個 Stream 變換為另外一個 Stream
而最后通過 subscribe 生成了 observer 觀察者,當(dāng)有事件發(fā)生時,observer 的回調(diào)函數(shù)會調(diào)用,打印 Log,即融合了觀察者模式。
那么函數(shù)式是如何應(yīng)用在 RxJS 里面的呢?細(xì)心地同學(xué)可能發(fā)現(xiàn)了,RxJS 其實提供了大量的 Operators,如 map、filter、scan 等,以 函數(shù)式/聲明式 的方式來操作 Stream,且操作之后生成一個新 Stream,不會突變原 Stream,此為融合了函數(shù)式編程思想。
主動式與響應(yīng)式
Proactive(主動式):即主動輪詢,不停的去問需求方以期完成任務(wù),常見的有設(shè)置一個定時器,不斷的去給服務(wù)器發(fā)請求詢問是否有新的內(nèi)容產(chǎn)生。
Reactive(響應(yīng)式):即有事件發(fā)生時,通知我完成任務(wù),常見的有 DOM 事件的監(jiān)聽與觸發(fā)、WebSocket 等
舉個例子??
完成目標(biāo):如平臺中的上課通知,如果服務(wù)端收到新的課程開始通知,對應(yīng)的客戶端需要展示這些上課通知。
通過主動式的方式我們會寫出如下代碼:
setInterval(async function () {
try {
const classroomNotification = await fetch('https://xxx');
// 后續(xù)操作
} catch(err) {}
, 3000)
上述代碼每隔 3S 去發(fā)一次請求,問一下服務(wù)端,現(xiàn)在數(shù)據(jù)有沒有更新,有就把數(shù)據(jù)給我。
通過響應(yīng)式的方式去實現(xiàn)上述邏輯可能是如下:
const socket = new WebSocket("ws://xxx");
socket.addEventListener('open', function() { // 連接成功,可以開始通訊 });
socket.addEventListener('message', function () {
// 收到服務(wù)端傳來的數(shù)據(jù),修改前端數(shù)據(jù),展示在前端
})
命令式與函數(shù)式
命令式:你命令機(jī)器去做事情(how),得到你想要的(what)
聲明式:你告訴機(jī)器你需要什么(what),讓機(jī)器想出如何去做(how)
舉個例子??
完成目標(biāo):拿到一個數(shù)組中的每項數(shù)字或包含數(shù)字的字符串,獲得這些數(shù)字乘以 2 之后相加的結(jié)果。
如果完成上述目標(biāo),我們用命令式的方式會寫出如下代碼:
const source = [1, 5, 9, 3, 'hi', 'tb', 456, '11', 'yoyoyo'];
let total = 0;
for (let i = 0; i < source.length; i++) {
let num = parseInt(source[i], 10);
if (!isNaN(num)) {
total += num * 2;
}
}
即一步步的告知計算機(jī)要做什么(how),如遍歷數(shù)組,對每一項進(jìn)行 parseInt 操作,判斷如果不是 NaN 時就相加,最后得到相加的結(jié)果(what)。
通過函數(shù)式或者聲明式的方式,我們會寫出如下代碼:
const source = [1, 5, 9, 3, 'hi', 'tb', 456, '11', 'yoyoyo'];
let total = source
.map(x => parseInt(x, 10))
.filter(x => !isNaN(x))
.map(x => x * 2)
.reduce((total, value) => total + value )
上面的代碼則是告知機(jī)器我想要什么(what),如我想要對數(shù)據(jù)進(jìn)行映射(map)、過濾(filter)、再映射(map)、最后進(jìn)行聚合(reduce)得到結(jié)果,由計算機(jī)自己想出如何進(jìn)行 map、filter、reduce 等操作,我不需要關(guān)心 map、filter、reduce 底層的實現(xiàn)細(xì)節(jié)。