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