個人博客:haichenyi.com。感謝關注
一. 目錄
- 一–目錄
- 二–架構模式的演變背景?
- 三–MVC:經典的分層起點?
- 四–MVP:面向接口的解耦嘗試?
- 五–MVVM:數據驅動的終極形態??
- 六–Vue:MVVM 的現代化實踐???
二. 架構模式的演變背景?
??在軟件開發中,架構模式是解決代碼組織,職責分離和可維護行的核心方案。一個"好"的架構可以少很多不必要的麻煩。這個"好"就很關鍵,雖然架構模式經歷了從MVC——>MVP——>MVVM的演變,但是,不一定后者比前者好。比方說:你一個小項目,MVC就夠用了,非要去使用MVP,MVVM,就會多寫很多無用代碼。要結合多方面去考慮。軟件開發的終極目標是高類聚,低耦合 。但是,還是需要結合實際項目去選擇。18年MVVM剛興起的時候,就寫過一篇三者的區別的文章
MVC、MVP、MVVM比較。回顧了一下,感覺還是適用的。
三. MVC:經典的分層起點
- 核心思想
- Model:管理數據和業務邏輯
- View:呈現給用戶看的界面
- Controller:接收用戶輸入,協調Model和View
- 典型流程
2.1 用戶點擊按鈕觸發點擊事件,傳遞到Controller
2.2 Controller觸發Model的事件去拿數據
2.2 Model數據更新之后,通知Controller更新view
//偽代碼如下
let tvContent = document.getElementById("tv_content")
document.getElementById("btn").onclick = function () {axios.post().then(res=>{let data = res.datatvContent.textContent = data})
}
//當前頁面獲取view的某個組件的引用
//用戶點擊按鈕,觸發網絡請求,拿到數據
//拿到數據之后,更新tvContent的內容
//當前頁面就是Controller,網絡請求包裝的類就是Model,組件就是view
//Controller控制Model,model拿到數據之后控制view刷新界面
//代碼少,邏輯簡單,看不出來啥問題。挺好用的。但是,代碼多,邏輯復雜呢?
//(前面就說了架構要根據實際項目情況來看)
- 局限性
- View和Model直接交互,耦合度高
- Controller臃腫:業務代碼全都寫在Controller中
四. MVP:面向接口的解耦嘗試?
- 核心改進
- Presenter(協調者):取代Controller,通過接口與view通信
- View被動化,只負責頁面更新,邏輯由Presenter處理
- 典型流程
2.1 View接收用戶操作,調用Presenter接口
2.2 Presenter操作Model處理數據
2.3 Model返回結果后,Presenter通過View接口更新頁面
//偽代碼如下,H5沒有接口的概念,我都不知道怎么舉例子
//這么理解,接口就是定義了一個一個的功能,需要有實現類去實現。
//還是上面的例子:用戶點擊按鈕更新頁面
interface IHome {update(content)
}class HomeImpl implements IHome {tvContent;constructor() {this.tvContent = document.getElementById("tv_content")}update(content: string) {this.tvContent.textContent = content}
}class HomePresenter {iHome: IHomeconstructor(iHome: IHome) {this.iHome = iHome}getData() {Axions.post().then(res=>{this.iHome.update(res.data)})}
}
let homePresenter = new HomePresenter(new HomeImpl())
document.getElementById("btn")?.onclick = function () {homePresenter.getData()
}
//上面的IHome接口,就是頁面更新的定義,HomeImpl就是這個接口的實現
//如果,頁面有很多種顯示需要處理,就需要定義多個方法,需要多個實現,以便于后面P層調用
//HomePresenter就是P層,負責數據處理,和使用接口的引用更新頁面
//按鈕點擊,觸發P層邏輯。
//這只是最簡單的寫法,有很多優化點。Android里面用的比較多
- 優勢與不足
- 優勢:View和Model完全解耦
- 不足:需要手動維護大量的接口,代碼冗余
五. MVVM:數據驅動的終極形態
- 核心思想
- ViewModel:作為view和model的橋梁,通過數據綁定實現自動同步
- 雙向綁定:view的輸入自動更新model,model數據變化自動刷新view
- 典型流程
2.1 view通過模板語法(如{{data}}),綁定到ViewModel
2.2 ViewModel監聽到Model變化,并轉換為View所需要的數據格式
2.3 用戶操作觸發ViewModel的方法,更新Model
//這么舉例子呢,其實,萬變不離其中,你不做的事情,都是框架幫你做的。
//比方說,Vue的響應式編程,你只用改變數據,頁面自動更新。啥都不干,頁面怎么可能知道數據遍了,從而自動更新呢?
//實際上就是發布訂閱者模式觸發更新,那么,是誰發布,誰訂閱呢?放到后面第六部分講
- 優勢
- 開發高效??:減少手動 DOM 操作,聚焦數據邏輯
- 動態更新??:適合實時數據展示
- 缺點:學習成本高,上手慢。原理復雜,遇到問題,不容易定位
六. Vue:MVVM 的現代化實踐
先說概念:Vue.js是mvvm模式集大成者,通過響應式系統和申明試模板,簡化了數據綁定。并引入組件化解決復雜場景的問題。
6.1 Vue中MVVM的映射關系
MVVM層 | Vue實現 |
---|---|
Model | data屬性 |
View | 模板(templtate)和樣式(style) |
ViewModel | Vue組件實例(暴露:data,computed,methods) |
6.2 雙向綁定的原理:Vue 的響應式系統通過以下步驟實現:
- 數據劫持
- vue2是使用 Object.defineProperty 監聽對象屬性。
- vue3是使用Proxy代理對象,支持深層次監聽
// Vue 2 數據劫持示例
function defineReactive(obj, key, val) {Object.defineProperty(obj, key, {get() {dep.depend(); // 收集依賴return val;},set(newVal) {val = newVal;dep.notify(); // 觸發更新}});
}
//這里插一句題外話,這個響應式的設計模式,就是觀察者模式
//數據本身就是被觀察者,get方法里面收集依賴,就是收集觀察者,set方法里面觸發更新,就是通知觀察者數據發生了變化
//vuex使用的是發布訂閱者模式。
//下一篇文章。就講講這兩種設計模式吧。
- 依賴收集
- 每個組件對應一個 ??Watcher??,在渲染時訪問數據屬性,觸發 getter 收集依賴。
- 分發更新
- 數據修改時觸發 setter,通知所有關聯的 Watcher 重新渲染(通過虛擬 DOM 對比更新真實 DOM,Diff算法)。
6.3 示例:數據變化驅動視圖更新?
<template><div><!-- 雙向綁定:v-model 語法糖 --><input v-model="message" /><p>{{ reversedMessage }}</p></div>
</template><script>
export default {data() {return { message: "Hello Vue!" }; // Model},computed: {reversedMessage() { // ViewModel 邏輯return this.message.split('').reverse().join('');}}
}
</script>
Vue 的雙向綁定?,解析如下:
HTML 模板?
<div id="app"><input v-model="message" /><p>{{ reversedMessage }}</p>
</div>
?JavaScript 邏輯?
// 模擬 Vue 實例
const data = { message: "Hello" };// 1. 數據劫持
defineReactive(data, "message", data.message);// 2. 計算屬性(依賴 message)
const computed = {reversedMessage: () => data.message.split("").reverse().join("")
};// 3. 模板渲染 Watcher
new Watcher(() => {//歸根結底,頁面更新的方法,還是這個。我們前面說的mvc,mvp頁面更新也是這個方法//所以,你以為它很神奇,其實,底層原理都是一樣的。主要還是看每個人的思路。document.querySelector("p").textContent = computed.reversedMessage();
});// 4. 雙向綁定(輸入框 → 數據)
document.querySelector("input").addEventListener("input", (e) => {data.message = e.target.value; // 修改數據 → 觸發 setter → 更新視圖
});
效果??:
- 輸入框內容變化時,data.message 更新。
- reversedMessage 自動重新計算,p標簽內容實時更新。