從零實現MVVM模式的Web前端框架的雛形

TNTWEB發表於2021-10-20

TNTWeb - 全稱騰訊新聞前端團隊,組內小夥伴在Web前端、NodeJS開發、UI設計、移動APP等大前端領域都有所實踐和積累。

目前團隊主要支援騰訊新聞各業務的前端開發,業務開發之餘也積累沉澱了一些前端基礎設施,賦能業務提效和產品創新。

團隊倡導開源共建,擁有各種技術大牛,團隊Github地址:https://github.com/tnfe

本文作者ancai,專案地址:https://github.com/ancai/nvm

image.png

一、前言

本文是自己在之前的業餘時間做過的一個小專案的實踐總結,主要是介紹關於現代Web前端框架的設計實現思路,參考了Vue2.x版本的設計思想。

二、MVVM模式

web開發的程式設計師對於MVC的設計思想應該並不陌生,網上相關的文章也有很多。M(model)-V(view)-C(Controller)代表了一種程式組織架構的模式,實現了資料、檢視和邏輯的有效解耦。M,表示資料狀態,V代表檢視介面,Controller用於處理資料和檢視之間的互動邏輯。後端的web框架(如Java中的springMVC、Struts2;nodejs中的express、koa等)依然沿用此設計模式至今。
不過在前端領域,涉及到使用者介面的程式設計,MVC的架構模式現在已經不是主流了。究其原因,主要有幾方面:

  • 業務邏輯和UI混合在一起
  • 不利於擴充套件和複用
  • 無法驗證測試
  • 增加資料的複雜性

image.png

後來微軟提出了MVVM的架構模式,其中和MVC的區別之處是VM取代了C,即ViewModel(檢視模型)替換了控制器。主要應用在使用者介面程式設計的領域,提供了雙向的資料繫結功能。View的變化會自動反映在ViewModel上,通過ViewModel來通知模型,資料模型Model變化時同樣通過VM來關聯到檢視。MVVM架構模式的出現給前端的開發帶來了新的生機,使得前端開發更容易編寫單元測試。同時藉助於ViewModel的中間橋樑,開發人員不用再關注一些DOM操作的互動細節,程式更簡潔和優雅,結合元件化的程式設計設計思想,前端模組更易複用和擴充。
ViewModel,一般由框架來實現,主要是提供了雙向資料繫結功能。
更多關於的比較推薦兩篇文章:什麼是MVVM框架?MVC vs MVVM: Key Differences with Examples

三、流程架構

專案裡面涉及到對模板、檢視、資料、指令以及依賴的管理。

image.png
從大的方面看,框架主要完成了模板和資料之間的雙向繫結。使用資料劫持的相關技術,對資料的變化的進行追蹤,同時引入釋出訂閱模式完成對屬性的依賴管理。另一方面則涉及模板的解析,提取模板中的指令和表示式,關聯到資料,來初始化檢視。模板和資料通過watcher銜接起來,這樣資料的變化直接對映到檢視;同時需要監聽檢視上的輸入相關的事件,這樣當輸入變化時直接對映到資料模型。

image.png
從工程處理的角度上看,可以分成四個步驟:初始化、資料監測、模板編譯和框架功能。初始化的過程主要是獲得呼叫方傳遞的配置資訊,模型資料和模板的預處理,某些關鍵屬性代理到上層。資料監測的環節,涉及對普通JavaScript物件的劫持處理,針對陣列的增強處理,通過一個釋出-訂閱模式完成對資料屬性的依賴注入,同時關聯到watcher。模板編譯環節,解析一個類似HTML的模板,提取裡面的變數表示式和相關指令,通過watcher關聯到資料模型。

image.png
上面給出了專案中的核心類圖,Observer實現對資料的劫持,Dep相當於是一個釋出-訂閱器同時關聯到Watcher和Observer,Compile負責解析模板,進一步提取獲得Directives類同時關聯到Parser和Watcher,Parser用於解析指令生產檢視UI,Watcher使用到一個Batcher來批量處理更新變化。

image.png
專案程式碼的目錄結構比較簡單,src目錄下總共有14個程式碼檔案,總程式碼行數在千行左右,理解起來不是很困難。index.js為入口檔案,模板編譯相關的處理邏輯均放在compile資料夾下,observer.js檔案放置資料劫持相關的處理邏輯,value.js裡面是字串型別的屬性表示式轉化為物件資料的處理邏輯,dep.js實現了一個簡單的釋出-訂閱模式來關聯資料狀態的變化以及watcher例項,watcher.js裡面有詳細的註釋說明在哪個環節被呼叫。另外,整個專案使用webpack簡單構建,打包後的體積也很小。

四、資料劫持實現

image.png
利用ES5中提供的object.defineProperty 這一API,重新定義get和set屬性訪問器實現對資料的劫持。主要是對普通物件和陣列的增強處理,監測資料的變化,通知依賴。在首次訪問屬性時注入依賴,通常發生在首次初始化模板時,獲得狀態屬性的表示式,在此時注入狀態屬性的依賴,這裡的依賴就是watcher例項;當資料發生變化時,通過dep的釋出-訂閱模式通知watcher例項,進而更新檢視狀態。

五、模板編譯實現

image.png
模板的編譯,沒有使用虛擬DOM結點,直接從原始的html模板入手,遍歷字串模板,收集裡面的指令。同時解析表示式,獲得對模型資料的依賴注入,在首次載入時,會關聯到watcher上。裡面針對不同型別的指令,對應不同的解析處理器,分為普通指令、事件指令、雙向繫結指令、if/for指令等。其中涉及到if/for的指令,需要建立新的執行上下文,關聯子級的環境資料。

六、關鍵程式碼實現

depwatcher

上面兩張圖時關於依賴管理器和watcher的簡單實現,依賴管理器可以視作一個釋出-訂閱模式的設計,即通過Dep類 watcher訂閱了模型資料,當模型資料變化時,watcher就知道了。有面圖時watcher的簡單處理邏輯,當模版編譯解析出表示式時,就會建立watcher的例項,同時訪問模型資料的值時,將該新建的watcher例項注入進去。就這樣,watcher例項充當了模版編譯和模型資料的橋樑。

七、總結

本專案沒有使用到virtual node(虛擬DOM結點),直接由字串模板編譯成了檢視部分。其中稍微有點難理解的部分:屬性表示式的提取解析、依賴的注入、if和for指令的處理、watcher的實現。
作為一個能力較弱的程式設計師,還無法像前端技術大牛那樣創造出一個成熟的前端框架。通過本文的簡單闡述,希望能幫助到有相同困惑的小夥伴加深對MVVM以及前端框架原理的理解,大牛請自動忽略。本專案的完整原始碼參見最後部分,給出了詳細的示例。

八、程式碼示例

原始碼

示例

九、團隊

TNTWeb - 騰訊新聞前端團隊,TNTWeb致力於行業前沿技術探索和團隊成員個人能力提升。為前端開發人員整理出了小程式以及web前端技術領域的最新優質內容,每週更新✨,歡迎star,github地址:https://github.com/tnfe/TNT-Weekly

logo.png

相關文章