【響應式編程的思維藝術】 (2)響應式Vs面向對象

来源:https://www.cnblogs.com/dashnowords/archive/2018/12/23/10163367.html
-Advertisement-
Play Games

本文是 "Rxjs 響應式編程 第一章:響應式" 這篇文章的學習筆記。 示例代碼地址: "【示例代碼】" 更多文章: "【《大史住在大前端》博文集目錄】" [TOC] 一. 劃重點 三句非常重要的話: 從理念上來理解,Rx模式引入了一種新的 “一切皆流” 的編程範式 從設計模式的角度來看, 是 發佈 ...


目錄

本文是Rxjs 響應式編程-第一章:響應式這篇文章的學習筆記。

示例代碼地址:【示例代碼】

更多文章:【《大史住在大前端》博文集目錄】

一. 劃重點

三句非常重要的話:

  • 從理念上來理解,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 小結

筆者只是初學,對響應式編程談不上什麼經驗,但程式的世界里終究是“沒有更好的技術,只有更適合的方案”,在合適的場景做到合適的技術選型才更重要,至於什麼樣的場景更適合響應式編程,還需要在後續的學習和實踐中慢慢體會,但無論如何,響應式編程中蘊含的工程思想和數學之美讓我贊嘆。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2017-09-23開始學習電腦專業 2018-12-23開始寫下第一篇博客,記錄自己在前端學習路上的歷程,分享自己的學習過程,為了更好的成長。 首先,來介紹以下HTML,請看: HTML 超文本標記語言,標準通用標記語言下的一個應用。“超文本”就是指頁面內可以包含圖片、鏈接,甚至音樂、程式等非文 ...
  • 這裡只是對自己的學習進行一次總結,也是為了讓自己以後如果長時間不使用快速記憶起來的筆記,如果想要學習,還是去官網看文檔比較好一些。、 註意 下麵的代碼的 script標簽的type類型都為 “text/babel” 目錄 一、無狀態組件 二、函數式聲明 三、this以及事件對象 四、操作dom元素 ...
  • 1.前言 在平時的業務開發中,前端通常需要請求後臺獲取數據,或者NodeJs讀取文件等等一系列的非同步操作,我們通常需要利用非同步操作的結果或者對非同步操作的結果進行處理。通常我們的解決方案是:在非同步操作成功或者失敗的回調函數裡面寫方法,在非同步操作比較簡單的時候這樣寫代碼還是比較好理解的,當業務逐漸趨於復 ...
  • 一、什麼是HTML? HTML:超文本標簽語言 (Hyper Text Markup Language) www萬維網的描述性語言。 XHTML指可擴展超文本標記語言(標識語言)(EXtensible HyperText Markup Language)是一種置標語言,表現方式與超文本標記語言(HT ...
  • 1、先寫結構 a.如果列表沒有時間 結構為:<li><a>新聞內容</a></li> b.如果列表有時間 結構為:<li><a>新聞內容</a><span>時間</span></li> 2、給li添加高度 3、若列表有時間,給a標簽,span標簽添加浮動 4、調整文本樣式(大小,顏色等) 5、以背景 ...
  • 函數式編程 filter的使用 reduce curry let dragon = (name,size,element) = console.log(dragon('fluffykins','tiny','lightling')) //curry let dragon = name = size ...
  • JavaScript 系列博客(五) 前言 本篇博客學習 js 選擇器來控制 css 和 html、使用事件(鉤子函數)來處理事件完成後完成指定功能以及js 事件控制頁面內容。 js 選擇器 在學習 js 選擇器前需要瞭解幾個概念。 節點(一):在文檔(document)中出現的所有內容都是 doc ...
  • 1.同源問題解決 首先,在同一個域下搭建網路功能變數名稱訪問,需要nginx軟體,下載之後修改部分配置 然後再終端下cmd nginx.exe命令,或者打開nginx.exe文件,會運行nginx一閃而過,在後臺運行而且一次是打開兩個的,可以在任務管理器控制結束進程, 接下來,你就可以打開8080介面給同域 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...