MVVM模式的理解
MVVM全稱Model-View-ViewModel是基於MVC和MVP體系結構模式的改進,MVVM就是MVC模式中的View的狀態和行為抽象化,將視圖UI和業務邏輯分開,更清楚地將用戶界面UI的開發與應用程序中業務邏輯和行為的開發區分開來。
描述
MVVM模式簡化了界面與業務的依賴,有助於將圖形用戶界面的開發與業務邏輯或數據模型的開發分離開來。在MVVM中的ViewModel作為綁定器將視圖層UI與數據層Model鏈接起來,在Model更新時,ViewModel通過綁定器將數據更新到View,在View觸發指令時,會通過ViewModel傳遞消息到Model,ViewModel像是一個黑盒,在開發過程中只需要關注於呈現UI的視圖層以及抽象模型的數據層Model,而不需要過多關注ViewModel是如何傳遞的數據以及消息。
組成
Model
- 以面向對象來對對事物進行抽象的結果,是代表真實狀態內容的領域模型。
- 也可以將
Model稱為數據層,其作為數據中心僅關注數據本身,不關注任何行為。
View
View是用戶在屏幕上看到的結構、布局和外觀,即視圖UI。- 當
Model進行更新的時候,ViewModel會通過數據綁定更新到View。
ViewModel
ViewModel是暴露公共屬性和命令的視圖的抽象。ViewModel中的綁定器在視圖和數據綁定器之間進行通信。- 在
Model更新時,ViewModel通過綁定器將數據更新到View,在View觸發指令時,會通過ViewModel傳遞消息到Model。
優點
- 低耦合: 視圖
View可以獨立於Model變化和修改,一個ViewModel可以綁定到不同的View上,當View變化的時候Model可以不變,當Model變化的時候View也可以不變。 - 可重用性: 可以把一些視圖邏輯放在一個
ViewModel里面,讓很多View重用這段視圖邏輯。 - 獨立開發: 開發人員可以專注於業務邏輯和數據的開發
Model,設計人員可以專注於頁面設計。 - 可測試: 界面素來是比較難於測試的,測試行為可以通過
ViewModel來進行。
不足
- 對於過大的項目,數據綁定需要花費更多的內存。
- 數據綁定使得
Bug較難被調試,當界面異常,可能是View的代碼有問題,也可能是Model的代碼有問題,數據綁定使得一個位置的Bug可能被快速傳遞到別的位置,要定位原始出問題的地方就變得不那么容易了。
實例
下面是參照Vue實現的簡單的數據綁定實例,當然對於Vue來說,文檔中也提到了Vue沒有完全遵循MVVM模型,但是Vue的設計也受到了其啟發,https://cn.vuejs.org/v2/guide/instance.html,關於為什么尤大說Vue沒有完全遵循MVVM,可以參考這個https://www.zhihu.com/question/327050991。
<!DOCTYPE html>
<html>
<head>
<title>數據綁定</title>
</head>
<body>
<div id="app">
<div>{{msg}}</div>
<div>{{date}}</div>
<button onclick="update()">update</button>
</div>
</body>
<script type="text/javascript">
///////////////////////////////////////////////////////////////////////////////
var Mvvm = function(config) {
this.$el = config.el;
this.__root = document.querySelector(this.$el);
this.__originHTML = this.__root.innerHTML;
function __dep(){
this.subscribers = [];
this.addSub = function(watcher){
if(__dep.target && !this.subscribers.includes(__dep.target) ) this.subscribers.push(watcher);
}
this.notifyAll = function(){
this.subscribers.forEach( watcher => watcher.update());
}
}
function __observe(obj){
for(let item in obj){
let dep = new __dep();
let value = obj[item];
if (Object.prototype.toString.call(value) === "[object Object]") __observe(value);
Object.defineProperty(obj, item, {
configurable: true,
enumerable: true,
get: function reactiveGetter() {
if(__dep.target) dep.addSub(__dep.target);
return value;
},
set: function reactiveSetter(newVal) {
if (value === newVal) return value;
value = newVal;
dep.notifyAll();
}
});
}
return obj;
}
this.$data = __observe(config.data);
function __proxy (target) {
for(let item in target){
Object.defineProperty(this, item, {
configurable: true,
enumerable: true,
get: function proxyGetter() {
return this.$data[item];
},
set: function proxySetter(newVal) {
this.$data[item] = newVal;
}
});
}
}
__proxy.call(this, config.data);
function __watcher(fn){
this.update = function(){
fn();
}
this.activeRun = function(){
__dep.target = this;
fn();
__dep.target = null;
}
this.activeRun();
}
new __watcher(() => {
console.log(this.msg, this.date);
})
new __watcher(() => {
var html = String(this.__originHTML||'').replace(/"/g,'\\"').replace(/\s+|\r|\t|\n/g, ' ')
.replace(/\{\{(.)*?\}\}/g, function(value){
return value.replace("{{",'"+(').replace("}}",')+"');
})
html = `var targetHTML = "${html}";return targetHTML;`;
var parsedHTML = new Function(...Object.keys(this.$data), html)(...Object.values(this.$data));
this.__root.innerHTML = parsedHTML;
})
}
///////////////////////////////////////////////////////////////////////////////
var vm = new Mvvm({
el: "#app",
data: {
msg: "1",
date: new Date(),
obj: {
a: 1,
b: 11
}
}
})
function update(){
vm.msg = "updated";
}
///////////////////////////////////////////////////////////////////////////////
</script>
</html>
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
https://zhuanlan.zhihu.com/p/38296857
https://baike.baidu.com/item/MVVM/96310
https://www.liaoxuefeng.com/wiki/1022910821149312/1108898947791072
