摘要:本文是響應(yīng)式編程第一章響應(yīng)式這篇文章的學(xué)習(xí)筆記。通過代碼對(duì)比可以發(fā)現(xiàn),在響應(yīng)式編程中,我們不再用對(duì)象的概念來對(duì)現(xiàn)實(shí)世界進(jìn)行建模,而是使用流的思想對(duì)信息進(jìn)行拆分和聚合。
本文是Rxjs 響應(yīng)式編程-第一章:響應(yīng)式這篇文章的學(xué)習(xí)筆記。示例代碼地址:【示例代碼】
更多文章:【《大史住在大前端》博文集目錄】
[TOC]
一. 劃重點(diǎn)三句非常重要的話:
從理念上來理解,Rx模式引入了一種新的“一切皆流”的編程范式
從設(shè)計(jì)模式的角度來看,Rx模式是發(fā)布訂閱模式和迭代器模式的組合使用
Rxjs對(duì)事件(流)的變換處理,可以對(duì)比lodash對(duì)數(shù)據(jù)的處理進(jìn)行理解。
原文對(duì)很多基礎(chǔ)卻核心的概念都有詳細(xì)的講解,本文不再贅述。需要注意的是,理解原理是一方面,但能夠熟練使用運(yùn)算符來轉(zhuǎn)換或查詢流信息是需要很長時(shí)間積累的,建議在學(xué)習(xí)過程中,每次遇到新的運(yùn)算符就主動(dòng)查閱資料理解其用法,這樣積少成多慢慢地就總結(jié)出開發(fā)模(tao)式(lu)了。
為了更直觀地感受面向?qū)ο蠛晚憫?yīng)式編程中的不同,筆者分別用兩種模式實(shí)現(xiàn)了兩個(gè)一樣的小動(dòng)畫,Demo比較簡單,就是一個(gè)不斷奔跑的角色和一個(gè)無限滾動(dòng)的背景圖。但是就體會(huì)和理解兩種開發(fā)模式而言基本夠用了。
二. 面向?qū)ο缶幊虒?shí)例 2.1 動(dòng)畫的基本編程范式動(dòng)畫實(shí)例使用canvas畫布來完成,簡單動(dòng)畫的基本編程模式如下:
//啟動(dòng)函數(shù) function startCanvasAnimation(){ //初始化舞臺(tái),舞臺(tái)對(duì)象(或者叫做精靈動(dòng)畫類,幀動(dòng)畫類) let background = new Background(ctx1,bgImg); let bird = new Bird(ctx1,roleImg); //把精靈動(dòng)畫實(shí)例集中管理 spirits.push(background); spirits.push(bird); //啟動(dòng)一個(gè)無限循環(huán)繪制暫態(tài)動(dòng)畫的遞歸函數(shù) return requestAnimationFrame(paint) } //每個(gè)繪制周期重復(fù)調(diào)用的繪制函數(shù) function paint() { //遍歷精靈動(dòng)畫實(shí)例集合 for(let spirit of spirits){ spirit.update();//更新自己的參數(shù) spirit.paint();//繪制精靈動(dòng)畫 } return requestAnimationFrame(paint);//尾遞歸調(diào)用繪制函數(shù) }
當(dāng)然示例中沒有涉及局部更新或其他有關(guān)渲染性能的部分,更復(fù)雜的動(dòng)畫需求可以直接使用引擎來實(shí)現(xiàn),這不是本篇的重點(diǎn)。
2.2 參考代碼/** * 角色類 */ class Role{ constructor(ctx,img){ this.ctx = ctx; //傳入畫布上下文實(shí)例 this.img = img; //傳入幀動(dòng)畫用的圖片 this.pos = [0,0]; //記錄幀動(dòng)畫初始位置 this.step = 68; //幀動(dòng)畫不同幀位置間距 this.index = 0; this.ratio = 4; } //更新自身狀態(tài) update(){ //此處通過速率控制實(shí)現(xiàn)了幀動(dòng)畫待繪制區(qū)域在雪碧圖中的起始位置 if (!(this.index++ % this.ratio)) { this.pos[1] = this.pos[1] === 748 ? 0 : this.pos[1] + this.step; } } //繪制 paint(){ //將角色繪制在畫布的指定位置 this.ctx.drawImage(this.img, this.pos[0] , this.pos[1] , 54 , 64 , 120 , 304, 54, 64); } }
背景也可以當(dāng)做是一個(gè)精靈動(dòng)畫實(shí)例,以同樣的模式定義即可,示例中的角色并沒有實(shí)現(xiàn)相對(duì)畫布的運(yùn)動(dòng)(也就是視差),感興趣的讀者可以自己嘗試實(shí)現(xiàn),完整的示例代碼見附件。
2.3 小結(jié)面向?qū)ο缶幊讨校唧w的精靈類可以繼承抽象精靈類,且將具體的實(shí)現(xiàn)封裝在自己的類定義中,最后使用類似于建造者模式的方法將各個(gè)實(shí)例組織起來,有面向?qū)ο缶幊探?jīng)驗(yàn)的讀者對(duì)這個(gè)流程應(yīng)該不會(huì)陌生。
三. 響應(yīng)式編程實(shí)現(xiàn)在響應(yīng)式編程中,我們需要構(gòu)建角色動(dòng)畫流和背景動(dòng)畫流這兩個(gè)可觀測(cè)對(duì)象,然后將這兩個(gè)流合并起來,此時(shí)就得到了一個(gè)尚未啟動(dòng)的動(dòng)畫信息流,通過subscribe( )方法啟動(dòng)這個(gè)流,并將繪制方法傳入回調(diào)函數(shù),就可以實(shí)現(xiàn)一個(gè)同樣的動(dòng)畫了。
/**動(dòng)畫的rxjs響應(yīng)式編程實(shí)現(xiàn)*/ //定義動(dòng)畫幀率 var rxjsRatio = 50; var rxjsFrame = parseInt(1000/rxjsRatio,10); //構(gòu)建角色動(dòng)畫流 var roleStream = Rx.Observable.interval(rxjsFrame).map(i=>{return {x:0,y:(i%12)*68}}); //構(gòu)建背景動(dòng)畫流 var bgiStream = Rx.Observable.interval(rxjsFrame).map(i=> i%800); //合并流 var rxjsAnim = Rx.Observable.combineLatest(roleStream,bgiStream,(role, bgi)=>{ return {role,bgi} }).subscribe(rxjsRender); //繪制角色 function rxjsPaintRole(rolePos) { ctx2.drawImage(roleImg, rolePos.x , rolePos.y , 54 , 64 , 120 , 304, 54, 64); } //繪制背景 function rxjsPaintBgi(offset) { let delta = 92; //繪制左半部分 ctx2.drawImage(bgImg , offset + delta , 0 , 800 + delta - offset , 576 , 0 , 0 , 800 + delta - offset , 400); //繪制右半部分 ctx2.drawImage(bgImg , delta, 0 , offset, 576 , 800 - offset , 0 , offset , 400); } //繪制 function rxjsRender(actors) { rxjsPaintBgi(actors.bgi); rxjsPaintRole(actors.role); }四. 差異對(duì)比 4.1 編程理念差異
面向?qū)ο缶幊?/strong>用類和繼承封裝多臺(tái)來聚合關(guān)系,響應(yīng)式編程用流和變換來聚合信息。
通過代碼對(duì)比可以發(fā)現(xiàn),在響應(yīng)式編程中,我們不再用對(duì)象的概念來對(duì)現(xiàn)實(shí)世界進(jìn)行建模,而是使用流的思想對(duì)信息進(jìn)行拆分和聚合。在面向?qū)ο缶幊讨校?strong>數(shù)據(jù)信息,數(shù)據(jù)更新方法,繪制方法這三大要素都是描述具體類的,他們被類的定義聚合在了一起;而在響應(yīng)式編程中,不再強(qiáng)調(diào)“關(guān)系”,而是將數(shù)據(jù)和變化聚合在一起,將處理方式聚合在一起。試想假如上面的示例中增加不同的類,障礙,怪物,積分等等,那么面向?qū)ο缶幊讨芯托枰黾有碌念惗x,而響應(yīng)式編程中就需要增加新的數(shù)據(jù)流,但是在每一個(gè)繪制的時(shí)間點(diǎn)拿到的暫態(tài)數(shù)據(jù)和根據(jù)這些暫態(tài)數(shù)據(jù)進(jìn)行的繪制動(dòng)作,其實(shí)都是一致的,區(qū)別只是關(guān)鍵信息的聚合方式不一樣了。
4.2 編程體驗(yàn)差異在傳統(tǒng)編程中,我們常常會(huì)得到一個(gè)無法直接用于最終場(chǎng)景的數(shù)據(jù)集合,然后需要手動(dòng)做一些后處理,最終把生成可被使用的數(shù)據(jù)提供給消費(fèi)模塊;而響應(yīng)式編程中強(qiáng)調(diào)的,是“直接告訴程序你最終想要獲得什么數(shù)據(jù)”,然后將程序的加工流程內(nèi)化到生產(chǎn)過程中,從而當(dāng)消費(fèi)模塊得到數(shù)據(jù)時(shí),直接就可以使用,而不需要再做更多的后處理,這對(duì)于消費(fèi)者來說無疑是體驗(yàn)的提升,就好像你去買組裝電腦時(shí),商家都會(huì)幫你推薦組件送貨上門還會(huì)幫你組裝好,你肯定感覺服務(wù)很到位,因?yàn)榇蟛糠秩说哪康氖鞘褂秒娔X,而不是享受買電腦的過程。
4.3 數(shù)學(xué)思想差異如果說面向?qū)ο缶幊趟枷胧窃诿枋隹陀^世界,那么響應(yīng)式編程就更像是在嘗試揭示規(guī)律。
回過頭再來看我們上面實(shí)現(xiàn)的Demo,在傳統(tǒng)的編程中,我們的思維模式更加傾向于一種微積分的思想,也就是說我們?cè)噲D描述一個(gè)精靈動(dòng)畫的變化時(shí),關(guān)注的是如何從x[i]得到x[i+1],當(dāng)我們得到這樣一個(gè)變換方法x[i+1]=g(x[i])后,只需要在對(duì)象的屬性中記錄每一個(gè)時(shí)刻的x[i],然后在下一個(gè)繪制周期開始時(shí)運(yùn)行這個(gè)方法計(jì)算出x[i+1],按照新的值繪制元素,用新值覆蓋舊值,然后循環(huán)這個(gè)過程就可以了;而在響應(yīng)式編程中,我們采取的方式是為x[i]求出一個(gè)通項(xiàng)公式,也就是x = f(i)這樣一種數(shù)學(xué)形式的描述,它們之間的關(guān)鍵區(qū)別并不是函數(shù)體內(nèi)邏輯的表達(dá)形式,而是在面向?qū)ο笾袑?shí)現(xiàn)的方法是有狀態(tài)的(你需要用某個(gè)實(shí)例屬性來標(biāo)記幀動(dòng)畫實(shí)例當(dāng)前的執(zhí)行狀態(tài)),而響應(yīng)式編程中的方法是無狀態(tài)的,是不是聯(lián)想到什么了?沒錯(cuò),函數(shù)式編程中的純函數(shù)。響應(yīng)式編程本來就是建立在函數(shù)式編程基礎(chǔ)之上的,只通過純函數(shù)實(shí)現(xiàn)集合的映射變換。
如果你聽說過傅里葉變換,應(yīng)該不難發(fā)現(xiàn)響應(yīng)式編程的思維模式和它很像,傅里葉變換可以將一個(gè)混雜的信號(hào),拆分成若干個(gè)不同振幅頻率和相位的正弦波的,這樣工程師就可以獨(dú)立分析自己感興趣的部分,這是信號(hào)分析中很基本的手段。在響應(yīng)式編程中,系統(tǒng)中的狀態(tài)變化以類似的方式被拆分成了很多獨(dú)立的流,如果開發(fā)者關(guān)注的某個(gè)流出現(xiàn)異常,只需要多帶帶關(guān)注其數(shù)據(jù)源和用于流變換的函數(shù)鏈即可(當(dāng)然它的數(shù)據(jù)源也可能會(huì)被拆分成若干個(gè)獨(dú)立的流),而不必陷入巨大的邏輯關(guān)系網(wǎng),這對(duì)于提升大型系統(tǒng)的調(diào)試效率來說是非常重要的。在面向?qū)ο缶幊讨?,這一點(diǎn)是很難做到的,更常見的情況是你修改了A方法,然后B方法就報(bào)錯(cuò)了,緊接著你發(fā)現(xiàn)這個(gè)過程竟然是遞歸的,最后程序崩潰了,你也崩潰了。
4.3 小結(jié)筆者只是初學(xué),對(duì)響應(yīng)式編程談不上什么經(jīng)驗(yàn),但程序的世界里終究是“沒有更好的技術(shù),只有更適合的方案”,在合適的場(chǎng)景做到合適的技術(shù)選型才更重要,至于什么樣的場(chǎng)景更適合響應(yīng)式編程,還需要在后續(xù)的學(xué)習(xí)和實(shí)踐中慢慢體會(huì),但無論如何,響應(yīng)式編程中蘊(yùn)含的工程思想和數(shù)學(xué)之美讓我贊嘆。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105300.html
摘要:由于技術(shù)棧的學(xué)習(xí),筆者需要在原來函數(shù)式編程知識(shí)的基礎(chǔ)上,學(xué)習(xí)的使用。筆者在社區(qū)發(fā)現(xiàn)了一個(gè)非常高質(zhì)量的響應(yīng)式編程系列教程共篇,從基礎(chǔ)概念到實(shí)際應(yīng)用講解的非常詳細(xì),有大量直觀的大理石圖來輔助理解流的處理,對(duì)培養(yǎng)響應(yīng)式編程的思維方式有很大幫助。 showImg(https://segmentfault.com/img/bVus8n); [TOC] 一. 響應(yīng)式編程 響應(yīng)式編程,也稱為流式編程...
摘要:響應(yīng)式命令式這兩種編程風(fēng)格的思維方式是完全相反的。第二種方式是工人主動(dòng)去找工人索取生產(chǎn)手機(jī)所要的零件,然后生產(chǎn)一臺(tái)完整的手機(jī),這兩種方式就對(duì)應(yīng)的響應(yīng)式和命令式。 angular2中內(nèi)置了rxjs,雖然框架本身并沒有強(qiáng)制開發(fā)者使用響應(yīng)式風(fēng)格來組織代碼,但是從框架開發(fā)團(tuán)隊(duì)的角度可以看出他們必然是認(rèn)同這種編程風(fēng)格的。rxjs本質(zhì)是基于函數(shù)式編程的響應(yīng)式風(fēng)格的庫,函數(shù)式相對(duì)于面向?qū)ο髞碚f更加抽...
摘要:本文是響應(yīng)式編程第四章構(gòu)建完整的應(yīng)用程序這篇文章的學(xué)習(xí)筆記。涉及的運(yùn)算符每隔指定時(shí)間將流中的數(shù)據(jù)以數(shù)組形式推送出去。中提供了一種叫做異步管道的模板語法,可以直接在的微語法中使用可觀測(cè)對(duì)象示例五一點(diǎn)建議一定要好好讀官方文檔。 本文是【Rxjs 響應(yīng)式編程-第四章 構(gòu)建完整的Web應(yīng)用程序】這篇文章的學(xué)習(xí)筆記。示例代碼托管在:http://www.github.com/dashnoword...
摘要:本文是響應(yīng)式編程第二章序列的深入研究這篇文章的學(xué)習(xí)筆記。函數(shù)科里化的基本應(yīng)用,也是函數(shù)式編程中運(yùn)算管道構(gòu)建的基本方法。四資料參考函數(shù)式編程指南 本文是Rxjs 響應(yīng)式編程-第二章:序列的深入研究這篇文章的學(xué)習(xí)筆記。示例代碼托管在:http://www.github.com/dashnowords/blogs 更多博文:《大史住在大前端》目錄 showImg(https://segme...
摘要:本文是響應(yīng)式編程第三章構(gòu)建并發(fā)程序這篇文章的學(xué)習(xí)筆記。筆者在自己的實(shí)現(xiàn)中又加入了右鍵切換飛船類型的功能,必須得說開發(fā)游戲的確比寫業(yè)務(wù)邏輯要有意思。由于沒有精確計(jì)算雪碧圖的坐標(biāo),所以在碰撞檢測(cè)時(shí)會(huì)有一些偏差。 本文是Rxjs 響應(yīng)式編程-第三章: 構(gòu)建并發(fā)程序這篇文章的學(xué)習(xí)筆記。示例代碼托管在:http://www.github.com/dashnowords/blogs 更多博文:《大...
閱讀 1371·2021-09-10 10:51
閱讀 2835·2019-08-30 15:54
閱讀 3377·2019-08-29 17:11
閱讀 936·2019-08-29 16:44
閱讀 1399·2019-08-29 13:47
閱讀 1095·2019-08-29 13:47
閱讀 1495·2019-08-29 12:23
閱讀 1052·2019-08-28 18:18