一.背景介紹
Rx(Reactive Extension -- 響應(yīng)式擴展 http://reactivex.io )最近在各個領(lǐng)域都非扯途校火。其實Rx這個貨是微軟在好多年前針對C#寫的一個開源類庫介评,但好多年都不溫不火椭更,一直到Netflix針對Java平臺做出了RxJava版本后才在開源社區(qū)熱度飛速躥升蚪燕。
二.知識剖析
我們從最基礎(chǔ)的異步回調(diào)講起,然后再從? Promise過渡到 RXJS锦募。
異步回調(diào):
在我們平時編程中摆屯,當(dāng)需要解決異步操作時,用得最多的應(yīng)該就是把回調(diào)函數(shù)當(dāng)做參數(shù)傳遞給異步函數(shù)了吧
function print_msg(msg) {
//Do something with msg
console.log(msg);
}
function async_read(callback) {
// Some async_work start
// Async get data from a PORT into msg
// Some async_work end
callback(msg);
}
async_read(print_msg);
在例1中御滩,我們通過傳遞 print_msg 這個回調(diào)函數(shù)給異步操作 async_read 以達到當(dāng)異步操作完成時輸出從端口讀到的 msg 這個目的鸥拧。
這樣看起來,這種方式簡單易懂削解,但是富弦,真的很好用嗎?想象一下氛驮,我們所傳遞的回調(diào)函數(shù)也是一個異步操作腕柜,也需要傳入一個回調(diào)函數(shù)來處理異步結(jié)果,如例子2:
function my_console(value2) {
//Do something with value2
console.log(value2);
}
function async_work1(callback1, callback2) {
// some async_work start
//......get value1
// some async_work end
callback1(value1, callback2);
}
function async_work2(value1, callback2) {
// some async_work start and do something with value1
//......get value2
// some async_work end
callback2(value2);
}
async_work1(async_work2, my_console);
在例2中,我們將回調(diào)函數(shù)1和回調(diào)函數(shù)2傳給了異步操作1盏缤,以便在異步操作1完成時調(diào)用回調(diào)函數(shù)1砰蠢,再將異步操作1得到的 value1 與回調(diào)函數(shù)2傳給回調(diào)函數(shù)1,最終當(dāng)回調(diào)函數(shù)1完成后將調(diào)用回調(diào)函數(shù)2輸出異步操作2得到的 value2 唉铜。當(dāng)回調(diào)函數(shù)2也是異步操作的時候怎么辦台舱?難道真的這樣一層套一層?這樣的代碼讀起來晦澀易懂潭流,我們要通過某種方式將其變得更簡單竞惋,于是 Promise 出現(xiàn)了!
Promise:
Promise 很好的將這種嵌套式調(diào)用轉(zhuǎn)變成了鏈?zhǔn)秸{(diào)用灰嫉,使得代碼的可讀性維護性都更高拆宛。對于例1,我們可以這樣:
var promise = new Promise(function(resolve, reject) {
// Some async_work start
// Async get data from a PORT into msg
// Some async_work end
if (/* Async_work successed */) {
resolve(msg);
}
else {
reject(error);
}
});
promise.then(function(msg) {
//Do something with msg
console.log(msg)
}, function(error) {
// Failure, do something here
console.log(error);
});
在上例中讼撒,我們創(chuàng)建了一個 Promise 對象浑厚,并且傳入了一個函數(shù)對象,注意這個函數(shù)并不是異步操作結(jié)束后將被調(diào)用的函數(shù)根盒,而是用來初始化 Promise 的钳幅。這個函數(shù)接受2個參數(shù) resolve 和 reject (后者可選),并且我們將異步操作全部移植入到這個函數(shù)中郑象,當(dāng)異步操作執(zhí)行成功之后贡这,調(diào)用了 resolve(msg)茬末,這是什么意思厂榛?很簡單,如果我們用第一種方法丽惭,resolve 這一行肯定是 callback(msg)击奶,也就是調(diào)用回調(diào)函數(shù)并將異步得到的 msg 傳給其。所以這里? resolve(msg) 的意思就是通知注冊號的回調(diào)函數(shù)異步操作已經(jīng)完成了责掏,并且產(chǎn)出了可用的 msg 參數(shù)柜砾。那么回調(diào)函數(shù)是在哪里注冊的呢?可用看到换衬,之后我們又調(diào)用了 promise 的 then 方法痰驱,它接收一個函數(shù)對象,不錯這個函數(shù)對象就是我們的回調(diào)函數(shù)瞳浦。但是我們的 then 居然接收了2個回調(diào)函數(shù)担映, 很顯然,第二個函數(shù)是用來處理 reject 通知過來的 error 的叫潦。
既然有了 Promise蝇完,那么何必再加入 RXJS 這個玩意呢?
Promise 有一個缺點,那便是一旦調(diào)用了 resolve 或者 reject 之后便返回了短蜕,不能再次 resolve 或者 reject氢架,想象一下,若是從端口源源不斷地發(fā)來消息朋魔,每次收到消息就要通知回調(diào)函數(shù)來處理岖研,那該怎么辦呢?
于是警检,偉大的 RXJS 又出現(xiàn)了6忻怠!
RXJS:
我們已經(jīng)知道了 Promise 的作用和用法解滓,通過 Promise 對象赃磨,我們可以在完成異步工作之后調(diào)用 resolve(X) 通知回調(diào)函數(shù)異步操作已經(jīng)完成了,并且生產(chǎn)了可使用的 X 對象洼裤。既然 RXJS 比 Promise 更厲害邻辉,那么它當(dāng)然也可以完成這個任務(wù),并且可以做得更好腮鞍。
先從官網(wǎng)搬來rxjs的幾個實例概念
Observable: 可觀察的數(shù)據(jù)序列.
Observer: 觀察者實例值骇,用來決定何時觀察指定數(shù)據(jù).
Subscription: 觀察數(shù)據(jù)序列返回訂閱實例.
Operators: Observable的操作方法,包括轉(zhuǎn)換數(shù)據(jù)序列移国,過濾等吱瘩,所有的Operators方法接受的參數(shù)是上一次發(fā)送的數(shù)據(jù)變更的值,而方法返回值我們稱之為發(fā)射新數(shù)據(jù)變更.
Subject: 被觀察對象.
Schedulers: 控制調(diào)度并發(fā)迹缀,即當(dāng)Observable接受Subject的變更響應(yīng)時使碾,可以通過scheduler設(shè)置響應(yīng)方式,目前內(nèi)置的響應(yīng)可以調(diào)用Object.keys(Rx.Subject)查看祝懂。
記得要添加庫
三.常見問題
Observable到底是什么
先上代碼:
let foo = Rx.Observable.create(observer => {
console.log('Hello');
observer.next(42);
});
foo.subscribe(x => console.log(x));
foo.subscribe(y => console.log(y));
這里可以把foo想象成一個函數(shù)票摇,這意味著你每次調(diào)用foo都會導(dǎo)致傳入Rx.Observable.create里的回調(diào)函數(shù)重新執(zhí)行一次, 調(diào)用的方式為foo.subscribe(callback), 相當(dāng)于foo()。 接收函數(shù)返回值的方式也從var a = foo()改為通過傳入回調(diào)函數(shù)的方式獲取砚蓬。第三行的observer.next表示返回一個值, 你可以調(diào)用多次矢门,每次調(diào)用observer.next后, 會先將next里的值返回給foo.subcribe里的回調(diào)函數(shù), 執(zhí)行完后再返回灰蛙。observer.complete, observer.error來控制流程祟剔。 具體看代碼:
var observable = Rx.Observable.create(observer => {
try {
observer.next(1);
console.log('hello');
observer.next(2);
observer.next(3);
observer.complete();
observer.next(4);
} catch (err) {
observer.error(err);
}
});
let subcription = observable.subscribe(value => {
console.log(value)
})
如上的第一個回調(diào)函數(shù)里的結(jié)構(gòu)是推薦的結(jié)構(gòu)。 當(dāng)observable的執(zhí)行出現(xiàn)異常的時候摩梧,通過observer.error將錯誤返回, 然而observable.subscribe的回調(diào)函數(shù)無法接收到.因為observer.complete已經(jīng)調(diào)用, 因此observer.next(4)的返回是無效的. Observable不是可以返回多個值的Promise物延。 雖然獲得Promise的值的方式也是通過then函數(shù)這種類似的方式, 但是new Promise(callback)里的callback回調(diào)永遠(yuǎn)只會執(zhí)行一次!因為Promise的狀態(tài)是不可逆的障本。
Observer是什么
先看代碼:
let foo = Rx.Observable.create(observer => {
console.log('Hello');
observer.next(42);
});
let observer = x => console.log(x);
foo.subscribe(observer);
代碼中的第二個變量就是observer. 沒錯教届, observer就是當(dāng)Observable"返回"值的時候接受那個值的函數(shù)!第五行中的observer其實就是通過foo.subscribe傳入的callback. 只不過稍加封裝了响鹃。 怎么封裝的? 看代碼:
var observable = Rx.Observable.create(observer => {
try {
observer.next(1);
console.log('hello');
observer.next(2);
observer.next(3);
observer.complete();
observer.next(4);
} catch(err) {
observer.error(err);
}
});
var observer = {
next(value) { console.log(value) ;},
complete() { console.log('completed');},
error(err) { console.error(err); }
}
let subcription = observable.subscribe(observer);
你看到observer被定義成了一個對象案训, 其實這才是完整的observer. 傳入一個callback到observable.subcribe相當(dāng)于傳入了 { next: callback }买置。
Subcription里的陷阱
Subscription是什么, 先上代碼:
var observable = Rx.Observable.interval(1000);
var subscription = observable.subscribe(x => console.log(x));
setTimeout(() => {
subscription.unsubscribe();
}, 3100)
Rx.Observable.interval可以返回一個能夠發(fā)射(返回)0强霎, 1忿项, 2, 3...城舞, n數(shù)字的Observable轩触, 返回的時間間隔這里是1000ms。 第二行中的變量就是subscription家夺。 subscription有一個unsubscribe方法, 這個方法可以讓subscription訂閱的observable發(fā)射的數(shù)據(jù)被observer忽略掉脱柱。 通俗點說就是取消訂閱。
unsubscribe存在一個陷阱拉馋。 先看代碼:
var foo = Rx.Observable.create((observer) => {
var i = 0
setInterval(() => {
observer.next(i++)
console.log('hello')
}, 1000)
})
const subcription = foo.subscribe((i) => console.log(i))
subcription.unsubscribe()
剛才說了榨为, unsubscribe只會讓observer忽略掉observable發(fā)射的數(shù)據(jù),但是setInterval依然會繼續(xù)執(zhí)行煌茴。 這看起來似乎是一個愚蠢的設(shè)計随闺。 所以不建議這樣寫。
Subject
Subject是一種能夠發(fā)射數(shù)據(jù)給多個observer的Observable, 這讓Subject看起來就好像是EventEmitter蔓腐。 先上代碼:
var subject = new Rx.Subject();
subject.subscribe({
next: (v) => console.log('observerA: ' + v)
});
subject.subscribe({
next: (v) => console.log('observerB: ' + v)
});
subject.next(1);
subject.next(2);
與Observable不同的是矩乐, Subject發(fā)射數(shù)據(jù)給多個observer。 其次回论, 定義subject的時候并沒有傳入callback散罕, 這是因為subject自帶next, complete, error等方法。從而可以發(fā)射數(shù)據(jù)給observer透葛。 這和EventEmitter很類似笨使。observer并不知道他subscribe的是Obervable還是Subject。 對observer來說是透明的僚害。 而且Subject還有各種派生, 比如說:
BehaviorSubject 能夠保留最近的數(shù)據(jù)繁调,使得當(dāng)有subscribe的時候萨蚕,立馬發(fā)射出去√阋龋看代碼:
ReplaySubject 能夠保留最近的一些數(shù)據(jù)岳遥, 使得當(dāng)有subscribe的時候,將這些數(shù)據(jù)發(fā)射出去裕寨。
AsyncSubject 只會發(fā)射結(jié)束前的一個數(shù)據(jù)
Multicasted Observables 是一種借助Subject來將數(shù)據(jù)發(fā)射給多個observer的Observable浩蓉。
四.解決方案
五.代碼實戰(zhàn)
我們來舉幾個例子派继。比如說在傳統(tǒng)的編程中 a=b+c,表示將表達式的結(jié)果賦給a捻艳,而之后改變b或c 的值不會影響a驾窟。但在響應(yīng)式編程中,a的值會隨著b或c的更新而更新认轨。
傳統(tǒng)編程中b,c的變化不會影響a
var a,b=1,c=2;
a=b+c;
console.log('b=' + b);
console.log('c=' + c);
console.log('a=' + a);
b=3;
c=2;
console.log('a=' + a);
那么用響應(yīng)式編程方法寫出來就是這個樣子绅络,可以看到隨著b和c的變化a也會隨之變化。
var b$ = Rx.Observable.from([1,3]);
var c$ = Rx.Observable.from([2,2]);
var a$ = Rx.Observable.zip(b$, c$, (b,c) => {
console.log('b=' + b);
console.log('c=' + c);
return b+c;
});
a$.subscribe(a=> console.log('a=' +a));
看出來一些不一樣的思維方式了嗎嘁字?響應(yīng)式編程需要描述數(shù)據(jù)流恩急,而不是單個點的數(shù)據(jù)變量,我們需要把數(shù)據(jù)的每個變化匯聚成一個數(shù)據(jù)流纪蜒。如果說傳統(tǒng)編程方式是基于離散的點衷恭,那么響應(yīng)式編程就是線。
上面的代碼雖然很短纯续,但體現(xiàn)出Rx的一些特點
Lamda表達式匾荆,對,就是那個看上去像箭頭的東西 => 杆烁。你可以把它想象成一個數(shù)據(jù)流的指向牙丽,我們從箭頭左方取得數(shù)據(jù)流,在右方做一系列處理后或者輸出成另一個數(shù)據(jù)流或者做一些其他對于數(shù)據(jù)的操作兔魂。
操作符:這個例子中的 from, zip 都是操作符烤芦。Rx中有太多的操作符,從大類上講分為:創(chuàng)建類操作符析校、變換類操作符构罗、過濾類操作符、合并類操作符智玻、錯誤處理類操作符遂唧、工具類操作符、條件型操作符吊奢、數(shù)學(xué)和聚集類操作符盖彭、連接型操作符等等。
Rx再體驗
首先在HTML中引入Rx類庫页滚,然后定義一個id為todo的文本輸入框:
在Javascript標(biāo)簽中選擇 ES6/Babel召边,因為這樣可以直接使用ES6的語法,在文本框中輸入以下javascript裹驰。在RxJS領(lǐng)域一般在Observable類型的變量后面加上$標(biāo)識這是一個“流變量”(由英文Stream得來隧熙,Observable就是一個Stream,所以用$標(biāo)識)幻林,不是必須的贞盯,但是屬于約定俗成音念。
let todo = document.getElementById('todo');
let input$ = Rx.Observable.fromEvent(todo, 'keyup');
input$.subscribe(input => console.log(input.target.value));
如果Console窗口默認(rèn)沒有打開的話,請點擊 Console 標(biāo)簽躏敢,然后選中右側(cè)的 Run with JS 旁邊的Auto-run js復(fù)選框闷愤。在Output窗口中應(yīng)該可以看到一個文本輸入框,在這個輸入框中輸入任意你要試驗的字符父丰,觀察Console
Console和Output窗口
這幾行代碼很簡單:首先我們得到HTML中id為todo的輸入框?qū)ο蟾翁罚缓蠖x一個觀察者對象將todo這個輸入框的keyup事件轉(zhuǎn)換成一個數(shù)據(jù)流,最后訂閱這個數(shù)據(jù)流并在console中輸出我們接收到的input事件的值蛾扇。我們從這個例子中可以觀察到幾個現(xiàn)象:
數(shù)據(jù)流:你每次在輸入框中輸入時都會有新的數(shù)據(jù)被推送過來攘烛。本例中,你會發(fā)現(xiàn)連續(xù)輸入“1镀首,2坟漱,3,4”更哄,在console的輸出是“1芋齿,12,123成翩,1234”觅捆,也就是說每次keyup事件我們都得到了完整的輸入框中的值。而且這個數(shù)據(jù)流是無限的麻敌,只要我們不停止訂閱栅炒,它就會一直在那里待命。
我們觀察的是todo上發(fā)生的keyup這個事件术羔,那如果我一直按著某個鍵不放會怎么樣呢赢赊?你的猜測是對的,一直按著的時候级历,數(shù)據(jù)流沒有更新释移,直到你抬起按鍵為止
如果觀察的足夠仔細(xì)的話,你會發(fā)現(xiàn)console中輸出的值其實是 input.target.value寥殖,我們觀察的對象其實是id為todo的這個對象上發(fā)生的keyup事件(Rx.Observable.fromEvent(todo, 'keyup'))玩讳。那么其實在訂閱的代碼段中的input其實是keyup事件才對。好扛禽,我們看看到底是什么锋边,將 console.log(input.target.value) 改寫成 console.log(input),看看會怎樣呢编曼?是的,我們得到的確實是KeyboardEvent
那么我們再來做幾個小練習(xí)剩辟,首先將代碼改成下面的樣子掐场,其實不用我講往扔,你應(yīng)該也可以猜得到,這是要過濾出 keyCode=32 的事件熊户,keyCode是Ascii碼萍膛,那么這就是要把空格濾出來
let todo = document.getElementById('todo');
let input$ = Rx.Observable.fromEvent(todo, 'keyup');
input$
.filter(ev=>ev.keyCode===32)
.subscribe(ev=>console.log(ev.target.value));
結(jié)果我們看到了,按123456789都沒有反應(yīng)嚷堡,直到按了空格
你可能一直在奇怪蝗罗,我們最終只對輸入框的值有興趣,能不能數(shù)據(jù)流只傳值過來呢蝌戒?當(dāng)然可以串塑,使用map這個變換類操作符就可以完成這個轉(zhuǎn)換了
let todo = document.getElementById('todo');
let input$ = Rx.Observable.fromEvent(todo, 'keyup');
input$
.map(ev=>ev.target.value*10)
.subscribe(value=>console.log(value));
map這個操作符做的事情就是允許你對原數(shù)據(jù)流中的每一個元素應(yīng)用一個函數(shù),然后返回并形成一個新的數(shù)據(jù)流北苟,這個數(shù)據(jù)流中的每一個元素都是原來的數(shù)據(jù)流中的元素應(yīng)用函數(shù)后的值桩匪。比如下面的例子,對于原數(shù)據(jù)流中的每個數(shù)應(yīng)用一個函數(shù)10*x友鼻,也就是擴大了10倍傻昙,形成一個新的數(shù)據(jù)流。
最常見的兩個操作符我們上面已經(jīng)了解了彩扔,我們繼續(xù)再來認(rèn)識新的操作符妆档。類似 .map(ev=>ev.target.value) 的場景太多了,以至于rxjs團隊搞出來一個專門的操作符來應(yīng)對虫碉,這個操作符就是 pluck贾惦。這個操作符專業(yè)從事從一系列嵌套的屬性種把值提取出來形成新的流。比如上面的例子可以改寫成下面的代碼蔗衡,效果是一樣的想括。那么如果其中某個屬性為空怎么辦?這個操作符負(fù)責(zé)返回一個 undefined 作為值加入流中瓦堵。
let todo = document.getElementById('todo');
let input$ = Rx.Observable.fromEvent(todo, 'keyup');
input$
.pluck('target', 'value')
.subscribe(value=>console.log(value));
這里解釋下用到的操作符(創(chuàng)建類操作符(from.fromEvent)借帘、變換類操作符(map)、過濾類操作符(filter)济蝉、合并類操作符(combineLatest.zip)杰刽、錯誤處理類操作符、工具類操作符王滤、條件型操作符贺嫂、數(shù)學(xué)和聚集類操作符、連接型操作符等等)
創(chuàng)建類操作符
通常來講雁乡,Rx團隊不鼓勵新手自己從0開始創(chuàng)建Observable第喳,因為狀態(tài)太復(fù)雜,會遺漏一些問題踱稍。Rx鼓勵的是通過已有的大量創(chuàng)建類轉(zhuǎn)換操作符來去建立Observable曲饱。比如 from 和 fromEvent悠抹。
from操作符
from 可以支持從數(shù)組、類似數(shù)組的對象扩淀、Promise楔敌、iterable 對象或類似Observable的對象(其實這個主要指ES2015中的Observable)來創(chuàng)建一個Observable。
這個操作符應(yīng)該是可以創(chuàng)建Observable的操作符中最常使用的一個驻谆,因為它幾乎可以把任何對象轉(zhuǎn)換成Observable卵凑。
var array = [10, 20, 30];
var result$ = Rx.Observable.from(array);
result$.subscribe(x => console.log(x));
fromEvent操作符
這個操作符是專門為事件轉(zhuǎn)換成Observable而制作的,非常強大且方便胜臊。對于前端來說勺卢,這個方法用于處理各種DOM中的事件再方便不過了。
var click$ = Rx.Observable.fromEvent(document, 'click');
click$.subscribe(x => console.log(x));
下面我們稍微給我們的頁面加點料区端,除了輸入框再加一個按鈕
在Javascript中我們同樣方法得到按鈕的DOM對象以及聲明對此按鈕點擊事件的觀察者:
let todo = document.getElementById('todo');
let input$ = Rx.Observable.fromEvent(todo, 'keyup');
input$
.pluck('target', 'value')
.subscribe(value=>console.log(value));
let addBtn = document.getElementById('addBtn');
let buttonClick$ = Rx.Observable.fromEvent(addBtn, 'click');
buttonClick$
.mapTo('clicked');
由于點擊事件沒有什么可見的值值漫,所以我們利用一個操作符叫 mapTo 把對應(yīng)的每次點擊轉(zhuǎn)換成字符 clicked。其實它也是一個 map 的簡化操作织盼。
合并類操作符
combineLatest操作符
既然現(xiàn)在我們已經(jīng)有了兩個流杨何,應(yīng)該試驗一下合并類操作符了,先來試試 combineLatest沥邻,我們合并了按鈕點擊事件的數(shù)據(jù)流和文本框輸入事件的數(shù)據(jù)流危虱,并且返回一個對象,這個對象有兩個屬性唐全,第一個是按鈕事件數(shù)據(jù)流的值埃跷,第二個是文本輸入事件數(shù)據(jù)流的值。也就是說應(yīng)該是類似 { ev: 'clicked', input: '1'} 這樣的結(jié)構(gòu)邮利。
let todo = document.getElementById('todo');
let input$ = Rx.Observable.fromEvent(todo, 'keyup');
// input$
//? .pluck('target', 'value')
//? .subscribe(value=>console.log(value));
let addBtn = document.getElementById('addBtn');
let buttonClick$ = Rx.Observable.fromEvent(addBtn, 'click');
// buttonClick$
//? .mapTo('clicked');
Rx.Observable.combineLatest(buttonClick$, input$, (ev, input)=>{
return {
ev: ev,
input: input
}
})
.subscribe(value => console.log(value))
那看看結(jié)果如何弥雹,在文本輸入框輸入1,沒反應(yīng)延届,再輸入2剪勿,還是沒反應(yīng)
那我們點擊一下按鈕試試,這回有結(jié)果了方庭,但有點沒明白為什么是12厕吉,輸入的數(shù)據(jù)流應(yīng)該是: 1,12械念,... 但那個1怎么丟了呢头朱?
再來文本框輸入3,4看看龄减,這回倒是都出來了
我們來解釋一下combineLatest的機制就會明白了项钮,如下圖所示,上面的2條線是2個源數(shù)據(jù)流(我們分別叫它們源1和源2吧),經(jīng)過combineLatest操作符后產(chǎn)生了最下面的數(shù)據(jù)流(我們稱它為結(jié)果流)寄纵。
當(dāng)源1的數(shù)據(jù)流發(fā)射時鳖敷,源2沒有數(shù)據(jù)脖苏,這時候結(jié)果流也不會有數(shù)據(jù)產(chǎn)生程拭,當(dāng)源2發(fā)射第一個數(shù)據(jù)(圖中A)后,combineLatest操作符做的處理是棍潘,把A和源1的最近產(chǎn)生的數(shù)據(jù)(圖中2)組合在一起恃鞋,形成結(jié)果流的第一個數(shù)據(jù)(圖中2A)。當(dāng)源2產(chǎn)生第二個數(shù)據(jù)(圖中B)時亦歉,源1這時沒有新的數(shù)據(jù)產(chǎn)生恤浪,那么還是用源1中最新的數(shù)據(jù)(圖中2)和源2中最新的數(shù)據(jù)(圖中B)組合。
也就是說 combineLatest 操作符其實是在組合2個源數(shù)據(jù)流中選擇最新的2個數(shù)據(jù)進行配對肴楷,如果其中一個源之前沒有任何數(shù)據(jù)產(chǎn)生水由,那么結(jié)果流也不會產(chǎn)生數(shù)據(jù)。
講到這里赛蔫,有童鞋會問砂客,原理是明白了,但什么樣的實際需求會需要這個操作符呢呵恢?其實有很多鞠值,我這里只舉一個小例子,現(xiàn)在健身這么熱渗钉,比如說我們做一個簡單的BMI計算器彤恶,BMI的計算公式是:體重(公斤)/(身高身高)(米米)。那么我們在頁面給出兩個輸入框和一個用于顯示結(jié)果的div:
那么在JS中鳄橘,我們想要達成的結(jié)果是只有兩個輸入框都有值的時候才能開始計算BMI声离,這時你發(fā)現(xiàn)combineLatest的邏輯不要太順溜啊。
let weight = document.getElementById('weight');
let height = document.getElementById('height');
let bmi = document.getElementById('bmi');
let weight$ = Rx.Observable
.fromEvent(weight, 'input')
.pluck('target', 'value');
let height$ = Rx.Observable
.fromEvent(height, 'input')
.pluck('target', 'value');
let bmi$ = Rx.Observable
.combineLatest(weight$, height$, (w, h) => w/(h*h/100/100));
bmi$.subscribe(b => bmi.innerHTML=b);
zip操作符
除了 combineLatest 瘫怜,Rxjs還提供了多個合并類的操作符术徊,我們再試驗一個 zip 操作符。 zip 和 combineLatest 非常像宝磨,但重要的區(qū)別點在于 zip 嚴(yán)格的需要多個源數(shù)據(jù)流中的每一個的相同順序的元素配對弧关。
比如說還是上面的例子,zip 要求源1的第一個數(shù)據(jù)和源2的第一個數(shù)據(jù)組成一對唤锉,產(chǎn)生結(jié)果流的第一個數(shù)據(jù)世囊;源1的第二個數(shù)據(jù)和源2的第二個數(shù)據(jù)組成一對,產(chǎn)生結(jié)果流的第二個數(shù)據(jù)窿祥。而 combineLatest 不需要等待另一個源數(shù)據(jù)流產(chǎn)生數(shù)據(jù)株憾,只要有一個產(chǎn)生,結(jié)果流就會產(chǎn)生。
zip 這個詞在英文中有拉鏈的意思嗤瞎,記住這個有助于我們理解這個操作符墙歪,就像拉鏈一樣,它需要拉鏈兩邊的齒一一對應(yīng)贝奇。從效果角度上講虹菲,這個操作符有減緩發(fā)射速度的作用,因為它會等待合并序列中最慢的那個掉瞳。
六.拓展思考
七.參考文獻
參考:如何理解Rxjs
參考:RXJS詳解
參考:Angular 從0到1:Rx--隱藏在Angular 中的利劍
參考:通俗的方式理解RxJS
參考:RxJS 核心概念Observer Subscription
參考:30天精通Rxjs
八.更多討論
武漢第149期PPT:鏈接 https://ptteng.github.io/PPT/PPT/JS-10-How-to-understand-RxJS.html#/
武漢第149期視頻連接:視頻https://v.qq.com/x/page/u0517ceol5p.html
------------------------------------------------------------------------------------------------------------------------
技能樹.IT修真院
“我們相信人人都可以成為一個工程師毕源,現(xiàn)在開始,找個師兄陕习,帶你入門霎褐,掌控自己學(xué)習(xí)的節(jié)奏,學(xué)習(xí)的路上不再迷酶昧停”冻璃。
這里是技能樹.IT修真院,成千上萬的師兄在這里找到了自己的學(xué)習(xí)路線损合,學(xué)習(xí)透明化,成長可見化拍埠,師兄1對1免費指導(dǎo)∶奕Γ快來與我一起學(xué)習(xí)吧 分瘾!